<!DOCTYPE html><html><head>
      <title>c++高级编程笔记</title>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      
      <link rel="stylesheet" href="file:///c:\Users\Unwarierbl\.vscode\extensions\shd101wyy.markdown-preview-enhanced-0.8.15\crossnote\dependencies\katex\katex.min.css">
      
      
      <script type="text/javascript" src="file:///c:\Users\Unwarierbl\.vscode\extensions\shd101wyy.markdown-preview-enhanced-0.8.15\crossnote\dependencies\mermaid\mermaid.min.js" charset="UTF-8"></script>
      
      
      <style>
      code[class*=language-],pre[class*=language-]{color:#c9d1d9;text-shadow:none;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-]::mozselection,code[class*=language-]::selection,pre[class*=language-]::mozselection,pre[class*=language-]::selection{text-shadow:none;background:#234879}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;background:#161b22}:not(pre)>code[class*=language-]{padding:.1em .3em;border-radius:.3em;color:#c9d1d9;background:#343942}pre[data-line]{position:relative}pre[class*=language-]>code[class*=language-]{position:relative;z-index:1}.line-highlight{position:absolute;left:0;right:0;padding:inherit 0;margin-top:1em;background:#2f2a1e;box-shadow:inset 5px 0 0 #674c16;z-index:0;pointer-events:none;line-height:inherit;white-space:pre}.namespace{opacity:.7}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8b949e}.token.punctuation{color:#c9d1d9}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#79c0ff}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#a5d6ff}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#a5d6ff}.token.atrule,.token.attr-value,.token.keyword{color:#f97583}.token.function{color:#d2a8ff}.token.important,.token.regex,.token.variable{color:#79b8ff}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}html body{font-family:'Helvetica Neue',Helvetica,'Segoe UI',Arial,freesans,sans-serif;font-size:16px;line-height:1.6;color:#ccc;background-color:#24292e;overflow:initial;box-sizing:border-box;word-wrap:break-word}html body>:first-child{margin-top:0}html body h1,html body h2,html body h3,html body h4,html body h5,html body h6{line-height:1.2;margin-top:1em;margin-bottom:16px;color:#fff}html body h1{font-size:2.25em;font-weight:300;padding-bottom:.3em}html body h2{font-size:1.75em;font-weight:400;padding-bottom:.3em}html body h3{font-size:1.5em;font-weight:500}html body h4{font-size:1.25em;font-weight:600}html body h5{font-size:1.1em;font-weight:600}html body h6{font-size:1em;font-weight:600}html body h1,html body h2,html body h3,html body h4,html body h5{font-weight:600}html body h5{font-size:1em}html body h6{color:#a3a3a3}html body strong{color:#fff}html body del{color:#a3a3a3}html body a:not([href]){color:inherit;text-decoration:none}html body a{color:#08c;text-decoration:none}html body a:hover{color:#00a3f5;text-decoration:none}html body img{max-width:100%}html body>p{margin-top:0;margin-bottom:16px;word-wrap:break-word}html body>ol,html body>ul{margin-bottom:16px}html body ol,html body ul{padding-left:2em}html body ol.no-list,html body ul.no-list{padding:0;list-style-type:none}html body ol ol,html body ol ul,html body ul ol,html body ul ul{margin-top:0;margin-bottom:0}html body li{margin-bottom:0}html body li.task-list-item{list-style:none}html body li>p{margin-top:0;margin-bottom:0}html body .task-list-item-checkbox{margin:0 .2em .25em -1.8em;vertical-align:middle}html body .task-list-item-checkbox:hover{cursor:pointer}html body blockquote{margin:16px 0;font-size:inherit;padding:0 15px;color:#a3a3a3;background-color:#363d45;border-left:4px solid #48525c}html body blockquote>:first-child{margin-top:0}html body blockquote>:last-child{margin-bottom:0}html body hr{height:4px;margin:32px 0;background-color:#48525c;border:0 none}html body table{margin:10px 0 15px 0;border-collapse:collapse;border-spacing:0;display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all}html body table th{font-weight:700;color:#fff}html body table td,html body table th{border:1px solid #48525c;padding:6px 13px}html body dl{padding:0}html body dl dt{padding:0;margin-top:16px;font-size:1em;font-style:italic;font-weight:700}html body dl dd{padding:0 16px;margin-bottom:16px}html body code{font-family:Menlo,Monaco,Consolas,'Courier New',monospace;font-size:.85em;color:#fff;background-color:#363d45;border-radius:3px;padding:.2em 0}html body code::after,html body code::before{letter-spacing:-.2em;content:'\00a0'}html body pre>code{padding:0;margin:0;word-break:normal;white-space:pre;background:0 0;border:0}html body .highlight{margin-bottom:16px}html body .highlight pre,html body pre{padding:1em;overflow:auto;line-height:1.45;border:#48525c;border-radius:3px}html body .highlight pre{margin-bottom:0;word-break:normal}html body pre code,html body pre tt{display:inline;max-width:initial;padding:0;margin:0;overflow:initial;line-height:inherit;word-wrap:normal;background-color:transparent;border:0}html body pre code:after,html body pre code:before,html body pre tt:after,html body pre tt:before{content:normal}html body blockquote,html body dl,html body ol,html body p,html body pre,html body ul{margin-top:0;margin-bottom:16px}html body kbd{color:#fff;border:1px solid #48525c;border-bottom:2px solid #3a424b;padding:2px 4px;background-color:#363d45;border-radius:3px}@media print{html body{background-color:#24292e}html body h1,html body h2,html body h3,html body h4,html body h5,html body h6{color:#fff;page-break-after:avoid}html body blockquote{color:#a3a3a3}html body pre{page-break-inside:avoid}html body table{display:table}html body img{display:block;max-width:100%;max-height:100%}html body code,html body pre{word-wrap:break-word;white-space:pre}}.markdown-preview{width:100%;height:100%;box-sizing:border-box}.markdown-preview ul{list-style:disc}.markdown-preview ul ul{list-style:circle}.markdown-preview ul ul ul{list-style:square}.markdown-preview ol{list-style:decimal}.markdown-preview ol ol,.markdown-preview ul ol{list-style-type:lower-roman}.markdown-preview ol ol ol,.markdown-preview ol ul ol,.markdown-preview ul ol ol,.markdown-preview ul ul ol{list-style-type:lower-alpha}.markdown-preview .newpage,.markdown-preview .pagebreak{page-break-before:always}.markdown-preview pre.line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}.markdown-preview pre.line-numbers>code{position:relative}.markdown-preview pre.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:1em;font-size:100%;left:0;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.markdown-preview pre.line-numbers .line-numbers-rows>span{pointer-events:none;display:block;counter-increment:linenumber}.markdown-preview pre.line-numbers .line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}.markdown-preview .mathjax-exps .MathJax_Display{text-align:center!important}.markdown-preview:not([data-for=preview]) .code-chunk .code-chunk-btn-group{display:none}.markdown-preview:not([data-for=preview]) .code-chunk .status{display:none}.markdown-preview:not([data-for=preview]) .code-chunk .output-div{margin-bottom:16px}.markdown-preview .md-toc{padding:0}.markdown-preview .md-toc .md-toc-link-wrapper .md-toc-link{display:inline;padding:.25rem 0}.markdown-preview .md-toc .md-toc-link-wrapper .md-toc-link div,.markdown-preview .md-toc .md-toc-link-wrapper .md-toc-link p{display:inline}.markdown-preview .md-toc .md-toc-link-wrapper.highlighted .md-toc-link{font-weight:800}.scrollbar-style::-webkit-scrollbar{width:8px}.scrollbar-style::-webkit-scrollbar-track{border-radius:10px;background-color:transparent}.scrollbar-style::-webkit-scrollbar-thumb{border-radius:5px;background-color:rgba(150,150,150,.66);border:4px solid rgba(150,150,150,.66);background-clip:content-box}html body[for=html-export]:not([data-presentation-mode]){position:relative;width:100%;height:100%;top:0;left:0;margin:0;padding:0;overflow:auto}html body[for=html-export]:not([data-presentation-mode]) .markdown-preview{position:relative;top:0;min-height:100vh}@media screen and (min-width:914px){html body[for=html-export]:not([data-presentation-mode]) .markdown-preview{padding:2em calc(50% - 457px + 2em)}}@media screen and (max-width:914px){html body[for=html-export]:not([data-presentation-mode]) .markdown-preview{padding:2em}}@media screen and (max-width:450px){html body[for=html-export]:not([data-presentation-mode]) .markdown-preview{font-size:14px!important;padding:1em}}@media print{html body[for=html-export]:not([data-presentation-mode]) #sidebar-toc-btn{display:none}}html body[for=html-export]:not([data-presentation-mode]) #sidebar-toc-btn{position:fixed;bottom:8px;left:8px;font-size:28px;cursor:pointer;color:inherit;z-index:99;width:32px;text-align:center;opacity:.4}html body[for=html-export]:not([data-presentation-mode])[html-show-sidebar-toc] #sidebar-toc-btn{opacity:1}html body[for=html-export]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc{position:fixed;top:0;left:0;width:300px;height:100%;padding:32px 0 48px 0;font-size:14px;box-shadow:0 0 4px rgba(150,150,150,.33);box-sizing:border-box;overflow:auto;background-color:inherit}html body[for=html-export]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc::-webkit-scrollbar{width:8px}html body[for=html-export]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc::-webkit-scrollbar-track{border-radius:10px;background-color:transparent}html body[for=html-export]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc::-webkit-scrollbar-thumb{border-radius:5px;background-color:rgba(150,150,150,.66);border:4px solid rgba(150,150,150,.66);background-clip:content-box}html body[for=html-export]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc a{text-decoration:none}html body[for=html-export]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc .md-toc{padding:0 16px}html body[for=html-export]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc .md-toc .md-toc-link-wrapper .md-toc-link{display:inline;padding:.25rem 0}html body[for=html-export]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc .md-toc .md-toc-link-wrapper .md-toc-link div,html body[for=html-export]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc .md-toc .md-toc-link-wrapper .md-toc-link p{display:inline}html body[for=html-export]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc .md-toc .md-toc-link-wrapper.highlighted .md-toc-link{font-weight:800}html body[for=html-export]:not([data-presentation-mode])[html-show-sidebar-toc] .markdown-preview{left:300px;width:calc(100% - 300px);padding:2em calc(50% - 457px - 300px / 2);margin:0;box-sizing:border-box}@media screen and (max-width:1274px){html body[for=html-export]:not([data-presentation-mode])[html-show-sidebar-toc] .markdown-preview{padding:2em}}@media screen and (max-width:450px){html body[for=html-export]:not([data-presentation-mode])[html-show-sidebar-toc] .markdown-preview{width:100%}}html body[for=html-export]:not([data-presentation-mode]):not([html-show-sidebar-toc]) .markdown-preview{left:50%;transform:translateX(-50%)}html body[for=html-export]:not([data-presentation-mode]):not([html-show-sidebar-toc]) .md-sidebar-toc{display:none}
/*
 * ------------------------------------------------------------------------
 * mdmdt-dark.css
 * (c) 2024
 * Author: cayxc
 * Homepage:  https://gitee.com/cayxc/mdmdt  https://github.com/cayxc/Mdmdt
 * License: Apache-2.0
 * ------------------------------------------------------------------------
*/
:root {
  --bg-color: #1b1b1f;
  --bg-color2: #282a32;
  --text-color: #d0d0d0;
  --text-grey: #464b50;
  --text-code: #bbc7fd;
  --title-color: var(--text-color);
  --border-color: var(--text-grey);
  --color-1: #3e69d7;
  --color-1-0-a: rgba(62, 105, 215, 0.3);
  --color-1-0-b: rgba(62, 105, 215, 0.12);
  --color-2: #f59102;
  --color-2-0-a: rgba(245, 145, 2, 0.3);
  --color-2-0-b: rgba(245, 145, 2, 0.12);
  --color-2-0-c: rgba(245, 145, 2, 0.4);
  --color-3: #03b736;
  --color-3-0-a: rgba(3, 183, 54, 0.3);
  --color-3-0-b: rgba(3, 183, 54, 0.12);
  --color-4: #8250df;
  --color-4-0-a: rgba(130, 80, 223, 0.3);
  --color-4-0-b: rgba(130, 80, 223, 0.12);
  --color-5: #e30f2e;
  --color-5-0-a: rgba(227, 15, 46, 0.3);
  --color-5-0-b: rgba(227, 15, 46, 0.12);
  --md-char-color: #667c89ff;
  --meta-content-color: var(--md-char-color);
  --primary-color: var(--color-1);
  --primary-btn-border-color: var(--bg-color2);
  --primary-btn-text-color: var(--text-color);
  --window-border: 1px solid var(--border-color);
  --active-file-bg-color: var(--bg-color2);
  --active-file-text-color: var(--color-1);
  --active-file-border-color: var(--color-1);
  --side-bar-bg-color: var(--bg-color);
  --item-hover-bg-color: var(--bg-color2);
  --item-hover-text-color: var(--color-1);
  --select-text-bg-color: var(--color-1-0-a);
  --monospace: monospace;
  --mermaid-theme: dark;
  /*or base, dark, forest, neutral, night */
  --mermaid-font-family: "trebuchet ms", verdana, arial, sans-serif;
  --mermaid-sequence-numbers: on;
  --mermaid-flowchart-curve: linear;
  --mermaid--gantt-left-padding: 75;
}
.markdown-preview.markdown-preview {
  /*
 * ------------------------
 * scroll style
 * ------------------------
*/
  /*
 * -----------------------------------
 * h1 ~ h6
 * p, strong, dl, em, u, kbd, hr, mark
 * -----------------------------------
*/
  /*
 * ------------------------
 * image
 * ------------------------
*/
  /*
 * ------------------------
 * a, link
 * ------------------------
*/
  /*
 * ------------------------
 * ul, ol
 * ------------------------
*/
  /*
 * ------------------------
 * code
 * ------------------------
*/
  /*
 * ------------------------
 * pre
 * ------------------------
*/
  /*windows下code字体*/
  /*
 * ------------------------
 * blockquote
 * ------------------------
*/
  /*
 * ------------------------
 * sup, sub, kbd
 * ------------------------
*/
  /*
 * ------------------------
 * table
 * ------------------------
*/
  /*
 * ------------------------
 * btn
 * ------------------------
*/
  /*
 * ------------------------
 * select
 * ------------------------
 */
  /*
 * ------------------------
 * textarea
 * ------------------------
 */
  /*
 * ------------------------
 * input
 * ------------------------
*/
  /*input checkbox*/
  /*md-htmlblock*/
  /* #write */
  /*
 * ------------------------
 * typora-source
 * ------------------------
*/
  /*
 * ------------------------
 * #toc-dropmenu
 * ------------------------
*/
  /*
 * ------------------------
 * sidebar
 * toc-content
 * ------------------------
*/
  /*ty-on-outline-filter(catalog search style)*/
  /*
 * --------------------------
 * file-library-list
 * file-library-search-result
 * --------------------------
*/
  /*ty-search-item*/
  /*
 * ------------------------
 * file-library tree
 * ------------------------
*/
  /*
 * ------------------------
 * md-notification-container
 * ------------------------
*/
  /*
 * ------------------------
 * TOC md-toc
 * ------------------------
*/
  /*
 * ------------------------
 * code colors
 * ------------------------
*/
  /* apply to code fences with plan text*/
  /*
 * ------------------------
 * export html
 * ------------------------
*/
  /**
 * --------------------------------------
 * unibody-window
 * Control UI on Windows/Linux (optional)
 * --------------------------------------
*/
  /* .dropdown-menu */
  /* .megamenu-men */
  /* context menu */
  /*
 * ------------------------
 * UI-controls
 * dialogs
 * ------------------------
*/
  /* footer */
  /**
 * ---------------------------------
 * Control UI on Mac (optional)
 * ---------------------------------
*/
  /*
 * ------------------------
 * #root
 * .ty-preferences
 * ------------------------
*/
  /*
 * ------------------------
 * diagram
 * ------------------------
*/
  /*
 * ------------------------
 * @media print
 * ------------------------
*/
  /*
 * ------------------------
 * @media screen
 * ------------------------
*/
}
.markdown-preview.markdown-preview html {
  font-size: 16px;
  background-color: var(--bg-color);
  color: var(--text-color);
  letter-spacing: 0.5px;
  -webkit-font-smoothing: antialiased;
}
.markdown-preview.markdown-preview body {
  font-size: 16px;
  line-height: 1.6;
  box-sizing: border-box;
  overflow-x: hidden;
  font-family: "PingFang SC", "Microsoft YaHei", Arial, Helvetica, "Helvetica Neue", sans-serif !important;
  background: var(--bg-color);
}
.markdown-preview.markdown-preview body.os-windows {
  font-family: "Microsoft YaHei", Helvetica, "Helvetica Neue", Arial, sans-serif !important;
}
.markdown-preview.markdown-preview ::-webkit-scrollbar {
  width: 8px !important;
  height: 8px !important;
}
.markdown-preview.markdown-preview ::-webkit-scrollbar-thumb {
  background: var(--text-grey) !important;
  border-radius: 4px !important;
}
.markdown-preview.markdown-preview ::-webkit-scrollbar-track {
  background: var(--bg-color2) !important;
}
.markdown-preview.markdown-preview body,
.markdown-preview.markdown-preview body div,
.markdown-preview.markdown-preview body pre,
.markdown-preview.markdown-preview body ul,
.markdown-preview.markdown-preview body section,
.markdown-preview.markdown-preview body content,
.markdown-preview.markdown-preview body #write {
  scrollbar-width: thin !important;
  scrollbar-color: var(--text-grey) var(--bg-color2) !important;
}
.markdown-preview.markdown-preview body,
.markdown-preview.markdown-preview #megamenu-content,
.markdown-preview.markdown-preview header {
  background: var(--bg-color) !important;
}
.markdown-preview.markdown-preview header {
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.16), 0 2px 5px 0 rgba(0, 0, 0, 0.1);
}
.markdown-preview.markdown-preview h1,
.markdown-preview.markdown-preview h2,
.markdown-preview.markdown-preview h3,
.markdown-preview.markdown-preview h4,
.markdown-preview.markdown-preview h5,
.markdown-preview.markdown-preview h6 {
  position: relative;
  line-height: 1.5;
  margin: 32px 0 16px;
  color: var(--title-color);
  letter-spacing: 2px;
}
.markdown-preview.markdown-preview h1 {
  font-size: 32px;
  border-bottom: 1px solid var(--border-color);
}
.markdown-preview.markdown-preview h2 {
  font-size: 28px;
}
.markdown-preview.markdown-preview h3 {
  font-size: 24px;
}
.markdown-preview.markdown-preview h4 {
  font-size: 20px;
}
.markdown-preview.markdown-preview h5 {
  font-size: 18px;
}
.markdown-preview.markdown-preview h6 {
  font-size: 16px;
}
.markdown-preview.markdown-preview h2.md-focus:before,
.markdown-preview.markdown-preview h3.md-focus:before,
.markdown-preview.markdown-preview h4.md-focus:before,
.markdown-preview.markdown-preview h5.md-focus:before,
.markdown-preview.markdown-preview h6.md-focus:before,
.markdown-preview.markdown-preview h2:before,
.markdown-preview.markdown-preview h3:before,
.markdown-preview.markdown-preview h4:before,
.markdown-preview.markdown-preview h5:before,
.markdown-preview.markdown-preview h6:before {
  height: 20px;
  position: absolute;
  left: -36px;
  float: left;
  padding-left: 6px;
  padding-right: 3px;
  border: 1px solid;
  border-radius: 3px;
  font-size: 12px;
  font-weight: 400;
  color: var(--color-1);
  display: none;
}
.markdown-preview.markdown-preview h2:before,
.markdown-preview.markdown-preview h2.md-focus:before {
  content: 'h2';
  top: 11px;
}
.markdown-preview.markdown-preview h3:before,
.markdown-preview.markdown-preview h3.md-focus:before {
  content: 'h3';
  top: 8px;
}
.markdown-preview.markdown-preview h4:before,
.markdown-preview.markdown-preview h4.md-focus:before {
  content: 'h4';
  top: 5px;
}
.markdown-preview.markdown-preview h5:before,
.markdown-preview.markdown-preview h5.md-focus:before {
  content: 'h5';
  top: 4px;
}
.markdown-preview.markdown-preview h6:before,
.markdown-preview.markdown-preview h6.md-focus:before {
  content: 'h6';
  top: 3px;
}
.markdown-preview.markdown-preview h2:hover::before,
.markdown-preview.markdown-preview h3:hover::before,
.markdown-preview.markdown-preview h4:hover::before,
.markdown-preview.markdown-preview h5:hover::before,
.markdown-preview.markdown-preview h6:hover::before {
  display: block;
}
.markdown-preview.markdown-preview hr {
  border: none;
  height: 1px;
  margin: 16px 0;
  background: var(--border-color);
  box-sizing: border-box;
}
.markdown-preview.markdown-preview p {
  margin: 16px 0;
}
.markdown-preview.markdown-preview strong {
  font-weight: bold;
  color: white;
}
.markdown-preview.markdown-preview u {
  text-decoration-color: var(--title-color);
  text-underline-offset: 4px;
}
.markdown-preview.markdown-preview em {
  font-weight: 400;
}
.markdown-preview.markdown-preview del {
  color: var(--text-grey);
  text-decoration: line-through;
  text-decoration-color: var(--color-5);
  -moz-text-decoration-color: var(--color-5);
}
.markdown-preview.markdown-preview mark {
  background-color: var(--color-1);
  color: var(--text-color);
  padding: 0 4px;
  border-radius: 5px;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}
.markdown-preview.markdown-preview img {
  display: block;
  max-width: 100%;
  border-radius: 8px;
  margin: 0 auto 16px;
  color: var(--md-char-color);
}
.markdown-preview.markdown-preview span.md-image,
.markdown-preview.markdown-preview span.md-image span.md-content,
.markdown-preview.markdown-preview span.md-image span.md-image-src-span,
.markdown-preview.markdown-preview span.md-image span.md-image-before-src,
.markdown-preview.markdown-preview span.md-image span.md-image-after-src,
.markdown-preview.markdown-preview span.md-image span.md-image-input-src-btn,
.markdown-preview.markdown-preview span.md-image span.md-image-pick-file-btn,
.markdown-preview.markdown-preview span.md-image span.md-before::before,
.markdown-preview.markdown-preview span.md-image span.md-image-input-src-btn::before,
.markdown-preview.markdown-preview span.md-image span.md-image-pick-file-btn::before {
  color: var(--color-1);
}
.markdown-preview.markdown-preview span.md-image-btn {
  background: var(--bg-color2);
}
.markdown-preview.markdown-preview span.md-image-btn:hover::before {
  color: var(--bg-color) !important;
}
.markdown-preview.markdown-preview span.md-image span.md-image-pick-file-btn {
  border-left-color: var(--border-color);
}
.markdown-preview.markdown-preview a {
  font-weight: 500;
  text-decoration: none;
  color: var(--color-1);
  font-size: 16px;
  text-underline-offset: 4px;
}
.markdown-preview.markdown-preview a:hover,
.markdown-preview.markdown-preview .md-link a:hover,
.markdown-preview.markdown-preview .footnotes a:hover {
  color: var(--color-1);
  text-decoration: underline;
  cursor: pointer !important;
}
.markdown-preview.markdown-preview .unibody-window a:hover,
.markdown-preview.markdown-preview content a:hover {
  color: var(--color-2) !important;
}
.markdown-preview.markdown-preview .footnotes,
.markdown-preview.markdown-preview .footnotes a {
  font-size: 14px;
  text-decoration: none;
}
.markdown-preview.markdown-preview .footnotes .md-def-name::before,
.markdown-preview.markdown-preview .footnotes .md-def-name::after {
  color: var(--text-color);
}
.markdown-preview.markdown-preview .footnotes .md-def-url,
.markdown-preview.markdown-preview .md-link .md-url {
  text-decoration: none;
  color: var(--color-1);
}
.markdown-preview.markdown-preview ul {
  margin-left: 0;
  margin-top: 5px;
  margin-bottom: 5px;
  padding-left: 36px;
}
.markdown-preview.markdown-preview ol {
  margin-left: 0;
  margin-top: 5px;
  margin-bottom: 5px;
  padding-left: 40px;
}
.markdown-preview.markdown-preview ul li,
.markdown-preview.markdown-preview ol li {
  margin-bottom: 5px;
}
.markdown-preview.markdown-preview ul > li > p {
  margin: 0 0 0 -2px;
}
.markdown-preview.markdown-preview ol > li > p {
  margin: 0 0 0 -6px;
}
.markdown-preview.markdown-preview ul > .task-list-item > input {
  margin-left: -22px;
}
.markdown-preview.markdown-preview ul ul {
  margin-left: -2px;
}
.markdown-preview.markdown-preview ol ol {
  margin-left: -7px;
}
.markdown-preview.markdown-preview ol > li > ul {
  margin-left: -7px;
}
.markdown-preview.markdown-preview ul > li > ol {
  margin-left: -2px;
}
.markdown-preview.markdown-preview code,
.markdown-preview.markdown-preview tt {
  margin: 16px 0;
  background: var(--bg-color2);
  font-size: 14px;
  box-sizing: border-box;
  border-radius: 5px;
  padding: 5px 10px;
  color: var(--text-code);
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}
.markdown-preview.markdown-preview p code,
.markdown-preview.markdown-preview p tt,
.markdown-preview.markdown-preview li code,
.markdown-preview.markdown-preview li tt,
.markdown-preview.markdown-preview blockquote code,
.markdown-preview.markdown-preview blockquote tt,
.markdown-preview.markdown-preview .md-alert code,
.markdown-preview.markdown-preview .md-alert tt,
.markdown-preview.markdown-preview table code,
.markdown-preview.markdown-preview table tt {
  padding: 3px 6px !important;
  background: var(--color-1-0-a);
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}
.markdown-preview.markdown-preview h1 code,
.markdown-preview.markdown-preview h2 code,
.markdown-preview.markdown-preview h3 code,
.markdown-preview.markdown-preview h4 code,
.markdown-preview.markdown-preview h5 code,
.markdown-preview.markdown-preview h6 code {
  vertical-align: middle;
}
.markdown-preview.markdown-preview h1 code {
  font-size: 18px;
}
.markdown-preview.markdown-preview h2 code {
  font-size: 16px;
}
.markdown-preview.markdown-preview .outline-content .outline-item code {
  font-size: 11px;
  padding: 2px 6px !important;
}
.markdown-preview.markdown-preview pre {
  margin: 16px 0;
  padding: 16px;
  background: var(--bg-color2) !important;
  font-size: 14px !important;
  box-sizing: border-box;
  border-radius: 8px;
  color: var(--text-code);
  overflow: auto;
}
.markdown-preview.markdown-preview pre pre {
  background: transparent !important;
  padding: 5px 10px;
  border: none;
}
.markdown-preview.markdown-preview pre .CodeMirror-sizer {
  padding-left: 10px;
}
.markdown-preview.markdown-preview .os-windows pre,
.markdown-preview.markdown-preview .os-windows code {
  font-family: initial !important;
}
.markdown-preview.markdown-preview blockquote,
.markdown-preview.markdown-preview .md-alert {
  margin: 16px 0;
  padding: 16px;
  background: var(--color-1-0-b);
  box-sizing: border-box;
  border-right: 10px solid transparent;
  border-left: 4px solid var(--color-1);
  border-radius: 8px;
}
.markdown-preview.markdown-preview blockquote blockquote {
  background: var(--color-1-0-b);
}
.markdown-preview.markdown-preview .md-alert.md-alert-note {
  border-left-color: var(--color-1);
  background: var(--color-1-0-b);
  color: var(--color-1) !important;
}
.markdown-preview.markdown-preview .md-alert.md-alert-note .md-alert-text-note {
  color: var(--color-1);
}
.markdown-preview.markdown-preview .md-alert.md-alert-caution {
  border-left-color: var(--color-2);
  background: var(--color-2-0-b);
  color: var(--color-2) !important;
}
.markdown-preview.markdown-preview .md-alert.md-alert-caution .md-alert-text-caution {
  color: var(--color-2);
}
.markdown-preview.markdown-preview .md-alert.md-alert-tip {
  border-left-color: var(--color-3);
  background: var(--color-3-0-b);
  color: var(--color-3) !important;
}
.markdown-preview.markdown-preview .md-alert.md-alert-tip .md-alert-text-tip {
  color: var(--color-3);
}
.markdown-preview.markdown-preview .md-alert.md-alert-important {
  border-left-color: var(--color-4);
  background: var(--color-4-0-b);
  color: var(--color-4) !important;
}
.markdown-preview.markdown-preview .md-alert.md-alert-important .md-alert-text-important {
  color: var(--color-4);
}
.markdown-preview.markdown-preview .md-alert.md-alert-warning {
  border-left-color: var(--color-5);
  background: var(--color-5-0-b);
  color: var(--color-5) !important;
}
.markdown-preview.markdown-preview .md-alert.md-alert-warning .md-alert-text-warning {
  color: var(--color-5);
}
.markdown-preview.markdown-preview sup,
.markdown-preview.markdown-preview sub {
  font-size: 12px;
}
.markdown-preview.markdown-preview sup.md-footnote {
  background: transparent;
  padding: 0 2px;
  color: var(--color-1);
}
.markdown-preview.markdown-preview sup.md-footnote:hover {
  color: var(--color-2);
  cursor: pointer;
}
.markdown-preview.markdown-preview kbd {
  display: inline-block;
  border: none;
  background: var(--color-2);
  color: #fafafa;
  border-image: none;
  border-radius: 5px;
  padding: 0 6px;
  font-size: 14px;
  box-shadow: none;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}
.markdown-preview.markdown-preview ::selection {
  background: var(--color-2-0-c);
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}
.markdown-preview.markdown-preview figure.table-figure {
  margin-top: 24px !important;
}
.markdown-preview.markdown-preview table {
  width: 100%;
  margin-top: 16px;
  text-align: left;
  border-collapse: separate;
  border-spacing: 0;
  border-radius: 8px;
  overflow: hidden;
  padding: 0 !important;
  border: 1px solid var(--border-color) !important;
}
.markdown-preview.markdown-preview table thead tr th {
  background: var(--bg-color2);
}
.markdown-preview.markdown-preview table tbody tr:nth-child(even) td {
  background: var(--bg-color2);
}
.markdown-preview.markdown-preview table tbody tr:nth-child(odd) td {
  background: var(--bg-color);
}
.markdown-preview.markdown-preview table tr th,
.markdown-preview.markdown-preview table tr td {
  padding: 10px !important;
}
.markdown-preview.markdown-preview table tr td,
.markdown-preview.markdown-preview table thead tr th {
  border-left: 1px solid var(--border-color) !important;
}
.markdown-preview.markdown-preview table tbody tr td:first-of-type,
.markdown-preview.markdown-preview table thead tr th:first-of-type {
  border-left: none !important;
}
.markdown-preview.markdown-preview .md-grid-board-wrap table {
  border: none !important;
  border-radius: 0;
}
.markdown-preview.markdown-preview .md-grid-board-wrap table tr th,
.markdown-preview.markdown-preview .md-grid-board-wrap table tr td {
  padding: 0 !important;
}
.markdown-preview.markdown-preview #md-grid-width,
.markdown-preview.markdown-preview #md-grid-height {
  text-align: center !important;
  padding: 1px 0;
}
.markdown-preview.markdown-preview button#md-resize-grid {
  display: block !important;
  width: 100%;
  margin: 5px auto 0;
  color: var(--bg-color);
}
.markdown-preview.markdown-preview button#md-resize-grid:hover {
  color: var(--color-1);
  border-color: var(--color-1);
}
.markdown-preview.markdown-preview .btn,
.markdown-preview.markdown-preview .btn-default,
.markdown-preview.markdown-preview .long-btn {
  border-radius: 5px !important;
  border: 1px solid var(--border-color) !important;
  background: var(--bg-color2) !important;
  outline: none !important;
}
.markdown-preview.markdown-preview .btn-primary {
  background: var(--color-1) !important;
  border-color: var(--color-1) !important;
  color: #fff;
}
.markdown-preview.markdown-preview .btn:hover,
.markdown-preview.markdown-preview .long-btn:hover {
  background: var(--color-1-0-b) !important;
  color: var(--color-1);
  border-color: var(--color-1) !important;
}
.markdown-preview.markdown-preview .btn:focus,
.markdown-preview.markdown-preview .long-btn:focus {
  outline: none !important;
}
.markdown-preview.markdown-preview .btn {
  margin: 2px;
  margin-right: 5px !important;
}
.markdown-preview.markdown-preview .btn:last-of-type {
  margin-right: 0;
}
.markdown-preview.markdown-preview .dropdown-toggle::after {
  font-size: 14px;
}
.markdown-preview.markdown-preview .dropdown-toggle:hover::after {
  color: var(--color-1);
}
.markdown-preview.markdown-preview select {
  border-radius: 5px !important;
  border: 1px solid var(--border-color) !important;
  padding: 6px !important;
}
.markdown-preview.markdown-preview option {
  background: var(--bg-color);
}
.markdown-preview.markdown-preview textarea {
  outline: none;
  padding: 5px;
  border-radius: 5px;
  border-color: var(--border-color);
  background: var(--bg-color) !important;
  color: var(--text-color) !important;
  font-family: "PingFang SC", "Helvetica Neue", Helvetica, "Microsoft YaHei", Arial, sans-serif !important;
}
.markdown-preview.markdown-preview html input,
.markdown-preview.markdown-preview input {
  border-radius: 5px !important;
  line-height: 1.2;
  position: relative;
  padding: 0 6px;
  outline: none !important;
  border: 1px solid var(--border-color);
}
.markdown-preview.markdown-preview html input:focus,
.markdown-preview.markdown-preview html textarea:focus,
.markdown-preview.markdown-preview html input[type="number"]:focus,
.markdown-preview.markdown-preview html input[type="search"]:focus,
.markdown-preview.markdown-preview html input[type="text"]:focus {
  border-color: var(--color-1) !important;
  box-shadow: none !important;
  outline: none !important;
}
.markdown-preview.markdown-preview input::placeholder {
  font-size: 14px;
  color: var(--text-grey) !important;
}
.markdown-preview.markdown-preview input[type="checkbox"],
.markdown-preview.markdown-preview input[type="radio"] {
  outline: none;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  -ms-appearance: none;
  width: 14px !important;
  height: 14px !important;
  padding: 0 !important;
  box-sizing: border-box;
  border: 1px solid var(--border-color) !important;
  border-radius: 3px !important;
  list-style: none;
  background: transparent !important;
  margin-right: 10px !important;
  margin-bottom: -2px !important;
}
.markdown-preview.markdown-preview input[type="checkbox"]:checked,
.markdown-preview.markdown-preview input[type="radio"]:checked {
  background: var(--color-1) !important;
  border-color: transparent !important;
}
.markdown-preview.markdown-preview input[type="checkbox"]:checked::after {
  content: "";
  display: block;
  width: 5px;
  height: 9px;
  background: transparent;
  border: 2px solid #fff;
  border-top: none;
  border-left: none;
  position: absolute;
  box-sizing: border-box;
  bottom: 2.5px;
  left: 3.5px;
  transform: rotate(40deg);
  -webkit-transform: rotate(40deg);
  -ms-transform: rotate(40deg);
}
.markdown-preview.markdown-preview input[type="radio"] {
  border-radius: 50% !important;
  -webkit-border-radius: 50% !important;
  -moz-border-radius: 50% !important;
  border: 1px solid var(--border-color) !important;
}
.markdown-preview.markdown-preview input[type="radio"]:checked::after {
  content: "";
  display: block;
  width: 5px;
  height: 5px;
  box-sizing: border-box;
  margin: 3.5px auto;
  background: #fff;
  border-radius: 50%;
  -webkit-border-radius: 50%;
  -moz-border-radius: 50%;
}
.markdown-preview.markdown-preview span.md-comment {
  color: var(--md-char-color);
  opacity: 1;
}
.markdown-preview.markdown-preview .md-image-btn.selected,
.markdown-preview.markdown-preview .md-image-btn:hover {
  background: var(--color-1);
}
.markdown-preview.markdown-preview #write pre.md-meta-block:empty:before {
  color: var(--text-code);
}
.markdown-preview.markdown-preview .md-htmlblock-panel,
.markdown-preview.markdown-preview .md-htmlblock-container,
.markdown-preview.markdown-preview .md-htmlblock-container .md-htmlblock-panel-placeholder {
  background: transparent;
}
.markdown-preview.markdown-preview .md-htmlblock-container,
.markdown-preview.markdown-preview .md-math-container {
  border-radius: 8px;
  background: var(--bg-color);
}
.markdown-preview.markdown-preview .md-math-container {
  width: 100%;
  padding: 5px 10px;
}
.markdown-preview.markdown-preview .md-rawblock-on-edit .md-htmlblock-panel,
.markdown-preview.markdown-preview .md-rawblock-on-edit .md-mathblock-panel {
  border-radius: 8px;
  background: var(--bg-color2);
  padding: 5px 10px;
}
.markdown-preview.markdown-preview .md-mathblock-panel .md-rawblock-control {
  background: transparent;
}
.markdown-preview.markdown-preview .md-mathblock-panel .code-tooltip {
  box-shadow: none;
  margin: 16px 6px 0;
  border-top: 1px solid var(--border-color);
  border-radius: 0;
}
.markdown-preview.markdown-preview .md-rawblock-on-edit .md-rawblock-input {
  padding: 0;
  background: transparent;
}
.markdown-preview.markdown-preview .md-rawblock .md-rawblock-tooltip {
  height: auto;
  margin-top: -10px;
  float: right;
  border-radius: 5px;
  background: var(--bg-color2);
  animation: none !important;
}
.markdown-preview.markdown-preview .md-rawblock .md-rawblock-tooltip span {
  padding-top: 4px;
  padding-bottom: 4px;
}
.markdown-preview.markdown-preview .md-rawblock .md-rawblock-tooltip-edit-btn:hover {
  background: transparent;
}
.markdown-preview.markdown-preview .md-htmlblock:hover .md-htmlblock-container,
.markdown-preview.markdown-preview .md-htmlblock:hover .md-rawblock-tooltip,
.markdown-preview.markdown-preview .md-math-block:hover .md-math-container,
.markdown-preview.markdown-preview .md-math-block:hover .md-rawblock-tooltip,
.markdown-preview.markdown-preview .md-rawblock-on-edit:hover .md-rawblock-tooltip {
  background: var(--bg-color2);
}
.markdown-preview.markdown-preview .md-inline-math script {
  color: #f9007c;
}
.markdown-preview.markdown-preview #write {
  width: 100%;
  margin: 0 auto;
  padding: 0 96px 32px;
  scroll-behavior: smooth;
  scroll-padding: 10px;
  overflow-x: scroll;
}
.markdown-preview.markdown-preview #typora-sidebar {
  border-right-color: var(--border-color);
}
.markdown-preview.markdown-preview #typora-source .CodeMirror-sizer {
  padding-right: 0 !important;
}
.markdown-preview.markdown-preview #typora-source .CodeMirror-lines {
  width: 100%;
  max-width: 1400px;
  margin: 0 auto;
  padding: 32px 100px;
  box-sizing: border-box;
}
.markdown-preview.markdown-preview #typora-source .CodeMirror-lines pre {
  font-size: 16px !important;
  border: none !important;
  padding: 8px 16px !important;
  background: var(--bg-color) !important;
}
.markdown-preview.markdown-preview #typora-source .CodeMirror-activeline pre {
  background: var(--bg-color2) !important;
  border-radius: 6px;
}
.markdown-preview.markdown-preview #typora-source .CodeMirror-activeline .CodeMirror-linebackground {
  background: transparent !important;
}
.markdown-preview.markdown-preview #typora-source .CodeMirror-lines .cm-header {
  color: var(--color-2);
}
.markdown-preview.markdown-preview .CodeMirror.cm-s-typora-default div.CodeMirror-cursor {
  border-left: 3px solid var(--color-2);
}
.markdown-preview.markdown-preview .cm-s-typora-default .cm-header,
.markdown-preview.markdown-preview .cm-s-typora-default .cm-property,
.markdown-preview.markdown-preview .cm-s-typora-default .cm-link {
  color: var(--color-2);
}
.markdown-preview.markdown-preview .cm-s-inner .cm-comment,
.markdown-preview.markdown-preview .cm-s-inner.cm-comment,
.markdown-preview.markdown-preview .cm-overlay {
  color: var(--md-char-color);
}
.markdown-preview.markdown-preview .cm-s-typora-default .cm-string {
  color: var(--color-1);
}
.markdown-preview.markdown-preview .cm-s-typora-default .cm-code,
.markdown-preview.markdown-preview .cm-s-typora-default .cm-comment {
  color: var(--text-code);
}
.markdown-preview.markdown-preview .cm-s-typora-default .cm-tag {
  color: #e31570;
}
.markdown-preview.markdown-preview .cm-attribute {
  color: var(--color-2);
}
.markdown-preview.markdown-preview #toc-dropmenu {
  border-top-left-radius: 8px;
  border-bottom-left-radius: 8px;
  top: calc(var(--title-bar-height) - 2px) !important;
  right: 18px;
}
.markdown-preview.markdown-preview #toc-dropmenu #pin-outline-btn {
  display: inline-block;
  top: 10px;
}
.markdown-preview.markdown-preview #toc-dropmenu .divider {
  margin-bottom: 0;
}
.markdown-preview.markdown-preview .outline-content {
  padding: 16px;
  font-size: 14px !important;
}
.markdown-preview.markdown-preview .outline-content li {
  position: relative;
  z-index: 30;
  line-height: 1.3 !important;
  margin-bottom: 0;
}
.markdown-preview.markdown-preview .outline-content li::before {
  content: '';
  width: 1px;
  height: calc(100% + 5px);
  position: absolute;
  z-index: 30;
  left: 0;
  top: 0;
  background: var(--border-color);
}
.markdown-preview.markdown-preview .outline-content > li:first-of-type::before {
  top: 0;
}
.markdown-preview.markdown-preview .outline-content li a {
  font-size: 14px !important;
}
.markdown-preview.markdown-preview .outline-content li ul {
  margin-left: 19px;
  position: relative;
  z-index: 48;
}
.markdown-preview.markdown-preview .outline-content ul li::before {
  content: '';
  width: 1px;
  height: calc(100% + 11px);
  top: -11px;
}
.markdown-preview.markdown-preview .outline-content ul > li:last-of-type::before {
  content: '';
  width: 1px;
  height: 11px;
  top: -11px;
}
.markdown-preview.markdown-preview .outline-content li .outline-item {
  margin: 0 0 3px 6px;
  padding: 4px 8px;
  position: relative;
  z-index: 50;
  border-radius: 5px;
  border: 1px solid transparent;
}
.markdown-preview.markdown-preview .outline-content li .outline-item:hover,
.markdown-preview.markdown-preview .outline-content li .outline-item-active {
  z-index: 70;
}
.markdown-preview.markdown-preview .outline-content .outline-label {
  margin: 0;
  padding: 0;
  position: relative;
  z-index: 70;
  background: var(--bg-color);
  text-decoration: none;
}
.markdown-preview.markdown-preview .outline-content li .outline-item::before {
  content: '';
  width: 12px;
  height: calc(50% + 12px);
  position: absolute;
  z-index: 38;
  left: -7px;
  top: -12px;
  background: transparent;
  border-left: 1px solid var(--border-color);
  border-bottom: 1px solid var(--border-color);
}
.markdown-preview.markdown-preview .outline-content > li:first-of-type > .outline-item::before {
  top: calc(50% - 1px) !important;
  border-top: 1px solid var(--border-color);
  border-bottom: none;
  border-top-left-radius: 3px;
  -webkit-border-top-left-radius: 3px;
  border-bottom-left-radius: 0;
  -webkit-border-bottom-left-radius: 0;
}
.markdown-preview.markdown-preview .outline-content > li:first-of-type > .outline-item::after {
  content: '';
  width: 1px;
  height: 100%;
  position: absolute;
  z-index: 35;
  left: -7px;
  top: -2px;
  background: var(--bg-color);
}
.markdown-preview.markdown-preview .outline-content > li:last-of-type > .outline-item::before,
.markdown-preview.markdown-preview .outline-content > li ul > li:last-of-type > .outline-item::before {
  z-index: 90;
  background: transparent;
  border-left: 1px solid var(--border-color);
  border-top: 1px solid var(--border-color);
  border-top: none !important;
  border-bottom-left-radius: 3px;
  -webkit-border-bottom-left-radius: 3px;
  border-top-left-radius: 0;
  -webkit-border-top-left-radius: 0;
}
.markdown-preview.markdown-preview .outline-content > li:only-of-type > .outline-item::before,
.markdown-preview.markdown-preview .outline-content > li:last-of-type::before {
  display: none;
}
.markdown-preview.markdown-preview .outline-content > li:only-of-type {
  margin-left: -6px;
}
.markdown-preview.markdown-preview .outline-expander {
  padding-left: 0;
  width: auto;
  height: 8px;
  background: transparent;
}
.markdown-preview.markdown-preview .outline-expander:before {
  font-size: 10px;
  padding: 0 3px 0 2px;
  margin-top: -2px;
  margin-left: -2px;
  background: transparent;
}
.markdown-preview.markdown-preview .outline-item:hover,
.markdown-preview.markdown-preview .outline-item:hover > .outline-label,
.markdown-preview.markdown-preview .outline-item:hover > .outline-expander:before,
.markdown-preview.markdown-preview .outline-item:hover > .outline-expander,
.markdown-preview.markdown-preview .outline-item-active,
.markdown-preview.markdown-preview .outline-item-active > span,
.markdown-preview.markdown-preview .outline-item-active > .outline-expander:before {
  background: var(--bg-color2) !important;
  color: var(--color-1) !important;
}
.markdown-preview.markdown-preview .outline-item:hover {
  z-index: 70;
}
.markdown-preview.markdown-preview .outline-item:hover::before,
.markdown-preview.markdown-preview .outline-item-active::before {
  width: 6px !important;
}
.markdown-preview.markdown-preview .outline-item-active .outline-expander {
  font-weight: bold !important;
}
.markdown-preview.markdown-preview .ty-on-outline-filter .outline-content li::before,
.markdown-preview.markdown-preview .ty-on-outline-filter .outline-content .outline-item::before,
.markdown-preview.markdown-preview .ty-on-outline-filter .outline-content .outline-item::after {
  display: none;
}
.markdown-preview.markdown-preview .ty-on-outline-filter .outline-content > li > ul,
.markdown-preview.markdown-preview .ty-on-outline-filter .outline-content .outline-item {
  margin-left: 0;
}
.markdown-preview.markdown-preview #file-library-list,
.markdown-preview.markdown-preview #file-library-search-result {
  padding: 12px;
}
.markdown-preview.markdown-preview #file-library-list .file-list-item,
.markdown-preview.markdown-preview #file-library-search-result .ty-search-item {
  margin-bottom: 8px;
  border: 1px solid transparent;
  border-bottom-color: var(--border-color);
}
.markdown-preview.markdown-preview #file-library-list .file-list-item {
  padding-left: 12px;
  padding-right: 12px;
}
.markdown-preview.markdown-preview #file-library-list .file-list-item.active,
.markdown-preview.markdown-preview #file-library-search-result .ty-search-item.active {
  border-radius: 5px;
  border: 1px solid var(--color-1);
}
.markdown-preview.markdown-preview #file-library-list .file-list-item.active {
  padding: 6px 12px;
}
.markdown-preview.markdown-preview #file-library-search-result .ty-search-item.active {
  padding: 6px 12px 6px 0;
}
.markdown-preview.markdown-preview #file-library-list .file-list-item:first-of-type.active,
.markdown-preview.markdown-preview #file-library-search-result .ty-search-item:first-of-type.active {
  margin-top: 0;
}
.markdown-preview.markdown-preview #file-library-list .file-list-item:hover,
.markdown-preview.markdown-preview #file-library-search-result .ty-search-item:hover {
  border-radius: 5px;
  background: var(--bg-color2);
  border-color: var(--border-color);
}
.markdown-preview.markdown-preview #file-library-list .file-list-item:hover {
  color: var(--color-1);
}
.markdown-preview.markdown-preview #file-library-search-result .ty-search-item.active {
  color: var(--text-color);
}
.markdown-preview.markdown-preview #file-library-search-result .ty-search-item.active .ty-search-item-line {
  white-space: normal;
  word-break: break-word;
}
.markdown-preview.markdown-preview .file-list-item-time {
  margin-right: 0;
  padding-right: 0;
}
.markdown-preview.markdown-preview .file-list-item-count {
  margin-right: 0;
  padding: 0 6px;
  border-radius: 3px;
}
.markdown-preview.markdown-preview #file-library-search-result .ty-search-item.active .file-list-item-count {
  padding-right: 0;
}
.markdown-preview.markdown-preview #sidebar-loading-template {
  padding: 8px;
  border-radius: 5px;
  margin-bottom: 5px;
  display: none;
}
.markdown-preview.markdown-preview .ty-file-search-match-text {
  background-color: var(--color-2-0-c);
}
.markdown-preview.markdown-preview #file-library-tree {
  padding-top: 8px;
  padding-left: 12px;
  padding-right: 12px;
  box-sizing: border-box;
}
.markdown-preview.markdown-preview .sidebar-tab-btn {
  font-size: 16px !important;
  line-height: 20px !important;
  vertical-align: text-top;
  margin-top: 12px !important;
  color: var(--title-color);
}
.markdown-preview.markdown-preview .sidebar-tab-btn:hover {
  color: var(--color-1);
}
.markdown-preview.markdown-preview .ty-sidebar-search-panel .searchpanel-search-option-btn {
  top: 8px;
}
.markdown-preview.markdown-preview .file-tree-node {
  position: relative;
  padding-left: 0;
  margin-left: 0;
}
.markdown-preview.markdown-preview .file-library-node .file-node-children {
  margin-left: 12px;
}
.markdown-preview.markdown-preview .file-node-expanded > .file-node-children {
  margin-left: 28px;
}
.markdown-preview.markdown-preview .file-tree-node.active > .file-node-background {
  border-radius: 5px;
  border: none;
}
.markdown-preview.markdown-preview .fa-folder:before,
.markdown-preview.markdown-preview .fa-folder:after,
.markdown-preview.markdown-preview .fa-caret-right,
.markdown-preview.markdown-preview .fa-caret-down {
  color: var(--text-grey);
}
.markdown-preview.markdown-preview .ty-on-outline-filter .ty-outline-hit {
  color: var(--color-1);
}
.markdown-preview.markdown-preview #md-notification {
  position: relative;
}
.markdown-preview.markdown-preview #md-notification::before {
  font-size: 18px;
  top: 13px;
}
.markdown-preview.markdown-preview #md-notification p {
  margin: auto;
  font-size: 14px;
}
.markdown-preview.markdown-preview #md-notification > p {
  margin-top: 10px;
}
.markdown-preview.markdown-preview #md-notification-content span {
  margin-bottom: 4px;
  margin-left: 8px;
}
.markdown-preview.markdown-preview .typora-search-spinner > div {
  background: var(--color-1);
}
.markdown-preview.markdown-preview .md-toc {
  font-size: 14px;
  margin: 20px 0 0 0;
  background: var(--bg-color2);
}
.markdown-preview.markdown-preview .md-toc-content {
  padding: 20px 30px;
  margin: 16px 0;
}
.markdown-preview.markdown-preview .md-toc-content .md-toc-item {
  margin-bottom: 6px;
}
.markdown-preview.markdown-preview #write div.md-toc-tooltip {
  position: relative;
  margin: 12px 0 6px;
}
.markdown-preview.markdown-preview #write div.md-toc-tooltip .md-delete-toc {
  padding: 0 12px;
  background: var(--bg-color);
}
.markdown-preview.markdown-preview #write div.md-toc-tooltip .md-delete-toc:hover {
  color: var(--color-1);
  background: var(--bg-color2);
}
.markdown-preview.markdown-preview .cm-s-inner {
  background-color: transparent !important;
  color: var(--text-code);
}
.markdown-preview.markdown-preview .cm-s-inner .CodeMirror-gutters {
  background: transparent !important;
  color: var(--text-code);
  border: none;
}
.markdown-preview.markdown-preview .cm-s-inner .CodeMirror-guttermarker,
.markdown-preview.markdown-preview .cm-s-inner .CodeMirror-guttermarker-subtle,
.markdown-preview.markdown-preview .cm-s-inner .CodeMirror-linenumber {
  color: var(--md-char-color);
}
.markdown-preview.markdown-preview .cm-s-inner .CodeMirror-cursor {
  border-left: 1px solid var(--border-color);
}
.markdown-preview.markdown-preview .cm-s-inner div.CodeMirror-selected,
.markdown-preview.markdown-preview .cm-s-inner.CodeMirror-focused div.CodeMirror-selected,
.markdown-preview.markdown-preview .cm-s-inner .CodeMirror-line::selection,
.markdown-preview.markdown-preview .cm-s-inner .CodeMirror-line > span::selection,
.markdown-preview.markdown-preview .cm-s-inner .CodeMirror-line > span > span::selection,
.markdown-preview.markdown-preview .cm-s-inner .CodeMirror-line::-moz-selection,
.markdown-preview.markdown-preview .cm-s-inner .CodeMirror-line > span::-moz-selection,
.markdown-preview.markdown-preview .cm-s-inner .CodeMirror-line > span > span::-moz-selection {
  background: var(--color-2-0-c);
}
.markdown-preview.markdown-preview .cm-s-inner .CodeMirror-activeline-background {
  background: transparent;
}
.markdown-preview.markdown-preview .cm-s-inner .cm-keyword {
  color: #bb59fd;
}
.markdown-preview.markdown-preview .cm-s-inner .cm-operator {
  color: #6ac0df;
}
.markdown-preview.markdown-preview .cm-s-inner .cm-variable {
  color: #f4395dff;
}
.markdown-preview.markdown-preview .cm-s-inner .cm-variable-2 {
  color: #e2785f;
}
.markdown-preview.markdown-preview .cm-s-inner .cm-variable-3 {
  color: #0d50cc;
}
.markdown-preview.markdown-preview .cm-s-inner .cm-builtin {
  color: #f61d78;
}
.markdown-preview.markdown-preview .cm-s-inner .cm-atom {
  color: #fa5336;
}
.markdown-preview.markdown-preview .cm-s-inner .cm-number {
  color: #f59102;
}
.markdown-preview.markdown-preview .cm-s-inner .cm-def {
  color: #4087fd;
}
.markdown-preview.markdown-preview .cm-s-inner .cm-string {
  color: #00a866;
}
.markdown-preview.markdown-preview .cm-s-inner .cm-string-2 {
  color: #01b001;
}
.markdown-preview.markdown-preview .cm-s-inner .cm-comment,
.markdown-preview.markdown-preview .cm-s-inner .cm-meta {
  color: var(--md-char-color);
}
.markdown-preview.markdown-preview .cm-s-inner .cm-attribute {
  color: #c08b01;
}
.markdown-preview.markdown-preview .cm-s-inner .cm-property {
  color: #1b9f72;
}
.markdown-preview.markdown-preview .cm-s-inner .cm-qualifier {
  color: #dc7b45;
}
.markdown-preview.markdown-preview .cm-s-inner .cm-tag {
  color: #e32e73;
}
.markdown-preview.markdown-preview .cm-s-inner .cm-tag.cm-bracket {
  color: #01a2da;
}
.markdown-preview.markdown-preview .cm-s-inner .CodeMirror-matchingbracket {
  text-decoration: underline;
  color: var(--text-code) !important;
}
.markdown-preview.markdown-preview .CodeMirror div.CodeMirror-cursor {
  border-color: var(--text-color);
}
.markdown-preview.markdown-preview .md-fences {
  background-color: var(--bg-color2);
  color: var(--text-grey);
}
.markdown-preview.markdown-preview .md-fences .code-tooltip {
  background-color: var(--bg-color2);
  color: var(--text-color);
  border-radius: 5px;
  z-index: 50;
  padding: 0;
  bottom: -2.9em;
}
.markdown-preview.markdown-preview .md-fences .code-tooltip input,
.markdown-preview.markdown-preview .md-fences .code-tooltip span {
  padding: 3px;
  border-radius: 5px;
}
.markdown-preview.markdown-preview body.typora-export {
  padding-left: 0;
  padding-right: 0;
}
.markdown-preview.markdown-preview .typora-export-content,
.markdown-preview.markdown-preview .typora-export-show-outline .typora-export-content {
  width: 100vw;
  max-width: 2560px;
  height: 100vh;
  margin: 0 auto;
}
.markdown-preview.markdown-preview .typora-export-sidebar {
  margin-right: 0;
  margin-top: 0;
  border-right: 1px solid var(--border-color);
}
.markdown-preview.markdown-preview .typora-export-sidebar .outline-content {
  padding-top: 18px;
}
.markdown-preview.markdown-preview .typora-export-sidebar .outline-content li ul {
  margin-left: 20px;
  z-index: 55;
}
.markdown-preview.markdown-preview .typora-export-sidebar .outline-content .outline-item-active > .outline-item {
  position: relative;
  z-index: 70;
}
.markdown-preview.markdown-preview .typora-export .outline-content li a {
  font-weight: normal;
  padding: 2px 0;
}
.markdown-preview.markdown-preview .typora-export-sidebar .outline-content li,
.markdown-preview.markdown-preview .typora-export-sidebar .outline-content li > .outline-item {
  background: var(--bg-color) !important;
  color: var(--text-color) !important;
}
.markdown-preview.markdown-preview .typora-export-sidebar .outline-content ul li::before,
.markdown-preview.markdown-preview .typora-export-sidebar .outline-content ul > li:last-of-type::before {
  top: 0;
}
.markdown-preview.markdown-preview .typora-export .outline-content li .outline-item::before {
  height: calc(50% + 13px);
  top: -13px;
}
.markdown-preview.markdown-preview .typora-export-no-collapse-outline .outline-content li .outline-item::before {
  height: calc(50% + 10px);
  top: -10px;
}
.markdown-preview.markdown-preview .typora-export .outline-expander::before {
  content: "";
  width: 7px;
  height: 7px;
  background: transparent;
  box-sizing: border-box;
  border-top: 1px solid var(--text-color);
  border-right: 1px solid var(--text-color);
  transform: rotate(45deg);
  -webkit-transform: rotate(45deg);
  -ms-transform: rotate(45deg);
  margin-right: 5px;
  margin-top: 1px;
}
.markdown-preview.markdown-preview .typora-export .outline-expander:hover::before {
  content: "";
  width: 7px;
  height: 7px;
  background: transparent;
  box-sizing: border-box;
  border-top: 1px solid var(--text-color);
  border-right: 1px solid var(--text-color);
  transform: rotate(135deg);
  -webkit-transform: rotate(135deg);
  -ms-transform: rotate(135deg);
  margin-right: 5px;
  margin-left: 2px;
  margin-top: -2px;
}
.markdown-preview.markdown-preview .typora-export .outline-item-open > .outline-item > .outline-expander::before {
  content: "";
  width: 7px;
  height: 7px;
  background: transparent;
  box-sizing: border-box;
  border-top: 1px solid var(--text-color);
  border-right: 1px solid var(--text-color);
  transform: rotate(135deg);
  -webkit-transform: rotate(135deg);
  -ms-transform: rotate(135deg);
  margin-right: 5px;
  margin-left: 2px;
  margin-top: -2px;
}
.markdown-preview.markdown-preview .typora-export-sidebar .outline-content li .outline-item:hover,
.markdown-preview.markdown-preview .typora-export-sidebar .outline-content .outline-item-active > .outline-item,
.markdown-preview.markdown-preview .typora-export-sidebar .outline-content .outline-item-active > .outline-item > .outline-label {
  background: var(--bg-color2) !important;
  color: var(--color-1) !important;
}
.markdown-preview.markdown-preview .typora-export-sidebar .outline-content li > .outline-item:hover > .outline-expander::before {
  border-color: var(--color-1);
}
.markdown-preview.markdown-preview .typora-export-sidebar .outline-item-active > .outline-item::before {
  width: 6px;
}
.markdown-preview.markdown-preview .typora-export .outline-item:hover {
  margin-right: 0;
}
.markdown-preview.markdown-preview .typora-export-sidebar .outline-item-active::before {
  width: 1px !important;
}
.markdown-preview.markdown-preview .typora-export-sidebar .outline-content .outline-item-active > .outline-item > .outline-expander::before {
  border-width: 2px !important;
  border-color: var(--color-1);
}
.markdown-preview.markdown-preview .typora-export-no-collapse-outline .outline-expander {
  display: none;
}
.markdown-preview.markdown-preview body.unibody-window,
.markdown-preview.markdown-preview body.typora-export {
  letter-spacing: 1px;
}
.markdown-preview.markdown-preview .unibody-window .outline-content li .outline-item::before {
  height: calc(50% + 13px);
  top: -13px;
}
.markdown-preview.markdown-preview .unibody-window #write ul,
.markdown-preview.markdown-preview .typora-export #write ul {
  padding-left: 34px;
}
.markdown-preview.markdown-preview .unibody-window #write ol,
.markdown-preview.markdown-preview .typora-export #write ol {
  padding-left: 36px;
}
.markdown-preview.markdown-preview .unibody-window #write ul ul,
.markdown-preview.markdown-preview .unibody-window #write ol ol,
.markdown-preview.markdown-preview .unibody-window #write ul > li > ol,
.markdown-preview.markdown-preview .unibody-window #write ol > li > ul,
.markdown-preview.markdown-preview .typora-export #write ul ul,
.markdown-preview.markdown-preview .typora-export #write ol ol,
.markdown-preview.markdown-preview .typora-export #write ul > li > ol,
.markdown-preview.markdown-preview .typora-export #write ol > li > ul {
  margin-left: 0;
}
.markdown-preview.markdown-preview .unibody-window #write ul > li > p,
.markdown-preview.markdown-preview .typora-export #write ul > li > p {
  margin: 0;
}
.markdown-preview.markdown-preview .unibody-window #write ol > li > p,
.markdown-preview.markdown-preview .typora-export #write ol > li > p {
  margin: 0 0 0 -2px;
}
.markdown-preview.markdown-preview .unibody-window #write ul > .task-list-item > input,
.markdown-preview.markdown-preview .typora-export #write ul > .task-list-item > input {
  margin-left: -22px;
}
.markdown-preview.markdown-preview .unibody-window #megamenu-menu-sidebar {
  --bg-color: #1b1b1f;
  --bg-color2: #282a32;
  --border-color: #3c3e46;
  overflow: hidden;
  border-right: 1px solid var(--border-color);
  box-shadow: none;
}
.markdown-preview.markdown-preview #megamenu-menu-sidebar .megamenu-menu-list {
  border: none;
}
.markdown-preview.markdown-preview #recent-file-panel-search-input,
.markdown-preview.markdown-preview .megamenu-menu-panel .btn,
.markdown-preview.markdown-preview .dropdown-menu li a {
  border-radius: 5px !important;
}
.markdown-preview.markdown-preview .unibody-window .long-btn {
  border-radius: 8px !important;
}
.markdown-preview.markdown-preview .dropdown-menu {
  padding: 6px !important;
  border-radius: 8px !important;
  border: 1px solid var(--border-color);
}
.markdown-preview.markdown-preview .dropdown-menu li {
  border-radius: 5px !important;
}
.markdown-preview.markdown-preview .ty-spell-check-panel-item {
  border-radius: 5px;
  margin-bottom: 4px;
}
.markdown-preview.markdown-preview .btn-split-group .dropdown-menu {
  margin-top: 0;
}
.markdown-preview.markdown-preview .input-group-content .dropdown-menu li:hover {
  border-radius: 5px;
  background: var(--bg-color2);
}
.markdown-preview.markdown-preview .dropdown-menu li:hover a {
  color: var(--color-1);
}
.markdown-preview.markdown-preview .dropdown-menu li a {
  font-size: 14px;
}
.markdown-preview.markdown-preview .dropdown-menu table th,
.markdown-preview.markdown-preview .dropdown-menu table td {
  padding: 5px !important;
}
.markdown-preview.markdown-preview #sidebar-files-menu li:hover a {
  color: var(--color-1) !important;
}
.markdown-preview.markdown-preview #close-sidebar-menu-btn:hover {
  color: var(--color-1);
}
.markdown-preview.markdown-preview .unibody-window .long-btn:hover span,
.markdown-preview.markdown-preview .unibody-window .long-btn:hover i,
.markdown-preview.markdown-preview .megamenu-menu-panel .btn:hover,
.markdown-preview.markdown-preview #megamenu-menu-header-title:hover,
.markdown-preview.markdown-preview .toolbar-icon:hover,
.markdown-preview.markdown-preview #megamenu-back-btn:hover i {
  color: var(--color-1) !important;
  text-decoration: none;
}
.markdown-preview.markdown-preview .unibody-window .long-btn:hover,
.markdown-preview.markdown-preview .megamenu-menu-panel .btn:hover,
.markdown-preview.markdown-preview #megamenu-back-btn:hover {
  border-color: var(--color-1);
  background: var(--bg-color2);
}
.markdown-preview.markdown-preview #w-traffic-lights span {
  background: transparent !important;
  border-radius: 0 !important;
  border: none !important;
}
.markdown-preview.markdown-preview #w-traffic-lights span:hover {
  background: var(--color-1-0-b) !important;
}
.markdown-preview.markdown-preview #w-traffic-lights #w-close:hover {
  background: var(--color-5) !important;
  color: #fff !important;
}
.markdown-preview.markdown-preview #top-titlebar .btn {
  margin: 0 !important;
}
.markdown-preview.markdown-preview .megamenu-menu-panel .btn {
  padding: 6px 12px;
}
.markdown-preview.markdown-preview .megamenu-content {
  padding-right: 30px;
}
.markdown-preview.markdown-preview #open-theme-folder-btn {
  margin-top: 48px;
}
.markdown-preview.markdown-preview .megamenu-menu {
  background: var(--bg-color);
}
.markdown-preview.markdown-preview .megamenu-menu-header {
  border-color: var(--bg-color2);
}
.markdown-preview.markdown-preview #megamenu-back-btn {
  border-radius: 5px;
  border-color: var(--border-color);
  margin-left: 10px;
}
.markdown-preview.markdown-preview #megamenu-menu-list {
  padding: 0 8px;
}
.markdown-preview.markdown-preview .megamenu-menu-list li {
  margin-top: 6px;
}
.markdown-preview.markdown-preview .megamenu-menu-list li a {
  font-size: 16px;
  border-radius: 5px;
  display: block;
  border: 1px solid transparent;
  line-height: 34px;
}
.markdown-preview.markdown-preview .megamenu-menu-list li a .fa {
  font-size: 18px;
  line-height: 38px;
  display: inline-block;
  margin-left: 0;
  opacity: 1;
}
.markdown-preview.markdown-preview .megamenu-menu-list li #m-saved .fa {
  font-size: 20px;
  line-height: 36px !important;
  color: var(--color-2);
}
.markdown-preview.markdown-preview .megamenu-menu-list li a:hover,
.markdown-preview.markdown-preview .megamenu-menu-list li a.active:hover {
  color: var(--color-1) !important;
  background: var(--bg-color2) !important;
  border: 1px solid var(--color-1);
}
.markdown-preview.markdown-preview .megamenu-menu-list li a:hover,
.markdown-preview.markdown-preview .megamenu-menu-list li a.active {
  background: var(--bg-color2);
}
.markdown-preview.markdown-preview .megamenu-menu-list li a.active {
  color: var(--color-1) !important;
}
@media (max-width: 530px) {
  .markdown-preview.markdown-preview #megamenu-back-btn {
    margin-left: 0;
  }
  .markdown-preview.markdown-preview .megamenu-menu-list li a i {
    margin-left: 10px;
  }
}
.markdown-preview.markdown-preview #recent-file-panel-action-btn-container {
  margin-right: 0;
}
.markdown-preview.markdown-preview #recent-file-panel-search-input {
  width: calc(100% - 55px);
}
.markdown-preview.markdown-preview #megamenu-clear-recet-documents {
  background: var(--bg-color2);
  padding: 5px 10px;
  border-radius: 5px;
  font-size: 14px;
}
.markdown-preview.markdown-preview #megamenu-clear-recet-documents:hover {
  color: var(--color-1);
}
.markdown-preview.markdown-preview .ty-show-search #info-panel-tab-search .info-panel-tab-border,
.markdown-preview.markdown-preview .ty-show-search #info-panel-tab-search .info-panel-tab-border,
.markdown-preview.markdown-preview .active-tab-files #info-panel-tab-file .info-panel-tab-border,
.markdown-preview.markdown-preview .active-tab-outline #info-panel-tab-outline .info-panel-tab-border {
  border-radius: 2px;
  height: 3px;
}
.markdown-preview.markdown-preview #file-library-search-input {
  border-radius: 0 !important;
  top: 4px;
}
.markdown-preview.markdown-preview #close-outline-filter-btn {
  top: 8px;
  border: none !important;
  background: transparent !important;
}
.markdown-preview.markdown-preview .unibody-window .sidebar-content {
  top: 58px;
}
.markdown-preview.markdown-preview .native-window #file-library-search-input,
.markdown-preview.markdown-preview .unibody-window #file-library-search-input {
  border-radius: 5px !important;
  height: 28px;
}
.markdown-preview.markdown-preview .megamenu-menu-panel {
  scrollbar-width: none;
}
.markdown-preview.markdown-preview #theme-preview-grid {
  max-width: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
  justify-content: space-between;
  grid-gap: 32px;
}
@media (max-width: 1200px) {
  .markdown-preview.markdown-preview #theme-preview-grid {
    grid-template-columns: 1fr 1fr 1fr;
  }
}
.markdown-preview.markdown-preview .theme-preview-div {
  border: 3px solid var(--border-color);
  padding: 4px;
  box-sizing: border-box;
  margin: 0;
  overflow: hidden;
  color: var(--color-1);
  border-radius: 8px;
}
.markdown-preview.markdown-preview .theme-preview-div:hover {
  border-color: var(--color-1);
}
.markdown-preview.markdown-preview .theme-preview-div.active {
  border-color: var(--color-2);
}
.markdown-preview.markdown-preview .theme-preview-div .fa {
  color: var(--color-2);
}
.markdown-preview.markdown-preview .theme-preview-content {
  width: 100%;
  height: 100%;
  border-radius: 5px;
}
.markdown-preview.markdown-preview #outline-btn:hover {
  color: var(--color-1) !important;
}
.markdown-preview.markdown-preview .context-menu {
  border-radius: 5px !important;
  padding: 5px !important;
  border: 1px solid var(--border-color);
}
.markdown-preview.markdown-preview .context-menu li a {
  display: inline-block;
  width: 100%;
  border-radius: 5px !important;
}
.markdown-preview.markdown-preview .context-menu > .active a {
  color: var(--color-1) !important;
}
.markdown-preview.markdown-preview .tb43e-d6bd-dbe4y {
  background: var(--bg-color2);
}
.markdown-preview.markdown-preview #md-searchpanel input,
.markdown-preview.markdown-preview #md-replace-type-label,
.markdown-preview.markdown-preview #search-panel-replace-btn,
.markdown-preview.markdown-preview .modal-content,
.markdown-preview.markdown-preview .modal-dialog,
.markdown-preview.markdown-preview .modal-title,
.markdown-preview.markdown-preview .modal-content {
  border-radius: 5px;
}
.markdown-preview.markdown-preview .modal-content {
  padding: 6px;
}
.markdown-preview.markdown-preview #sidebar-files-menu li a {
  padding-top: 5px;
  padding-bottom: 5px;
}
.markdown-preview.markdown-preview #sidebar-files-menu .file-action-item {
  line-height: 30px;
}
.markdown-preview.markdown-preview #footer-word-count-info {
  border-radius: 5px;
  padding: 6px;
  border: 1px solid var(--border-color);
}
.markdown-preview.markdown-preview #footer-word-count-info table {
  border-radius: 0;
}
.markdown-preview.markdown-preview #footer-word-count-info table tr > td:first-of-type {
  border-top-left-radius: 5px;
  border-bottom-left-radius: 5px;
}
.markdown-preview.markdown-preview #footer-word-count-info table tr > td:last-of-type {
  border-top-right-radius: 5px;
  border-bottom-right-radius: 5px;
}
.markdown-preview.markdown-preview #typora-quick-open {
  background-color: var(--bg-color);
  border: 1px solid var(--border-color);
  padding: 12px;
  border-radius: 8px;
}
.markdown-preview.markdown-preview .typora-quick-open-item,
.markdown-preview.markdown-preview .md-hover-tip .code-tooltip-content {
  border-radius: 5px;
  overflow-x: scroll;
}
.markdown-preview.markdown-preview .code-tooltip.md-hover-tip,
.markdown-preview.markdown-preview .md-hover-tip .code-tooltip-content,
.markdown-preview.markdown-preview .md-arrow::after {
  border-bottom-color: var(--color-1) !important;
  background: var(--color-1) !important;
  box-shadow: 0 1px 4px var(--color-1) !important;
}
.markdown-preview.markdown-preview .md-hover-tip .md-arrow::after {
  box-shadow: none !important;
}
.markdown-preview.markdown-preview .md-hover-tip .code-tooltip-content:hover a {
  color: var(--color-2) !important;
}
.markdown-preview.markdown-preview .sidebar {
  position: relative;
}
.markdown-preview.markdown-preview .sidebar:after {
  content: '';
  display: block;
  position: absolute;
  width: 1px;
  height: 100%;
  right: -24px;
  top: 0;
  background: var(--border-color);
}
.markdown-preview.markdown-preview .ty-preferences a {
  font-size: 12px !important;
  color: var(--color-1);
  text-decoration: none;
}
.markdown-preview.markdown-preview .ty-preferences a:hover {
  text-decoration: underline;
  color: var(--color-2);
}
.markdown-preview.markdown-preview .export-item.active,
.markdown-preview.markdown-preview .export-items-list-control {
  border-radius: 3px !important;
}
.markdown-preview.markdown-preview .nav-group-item {
  border-radius: 5px !important;
}
.markdown-preview.markdown-preview .input-group table,
.markdown-preview.markdown-preview .export-detail {
  border-radius: 5px !important;
}
.markdown-preview.markdown-preview .export-detail .file-input input {
  padding-left: 8px !important;
  height: 30px !important;
  border-color: var(--border-color) !important;
}
.markdown-preview.markdown-preview .search-input {
  padding: 6px 12px !important;
  border-color: var(--border-color) !important;
}
.markdown-preview.markdown-preview .label-input-group div {
  border: none !important;
  margin: 0 !important;
  padding: 0 !important;
}
.markdown-preview.markdown-preview .label-input-group div pre {
  border-radius: 5px !important;
  font-size: 14px !important;
}
.markdown-preview.markdown-preview .input-group-content {
  border-radius: 5px !important;
}
.markdown-preview.markdown-preview .input-group-content table {
  margin-top: 4px;
}
.markdown-preview.markdown-preview .label-hint svg {
  margin-right: 3px;
}
.markdown-preview.markdown-preview .export-detail .input-group-content > .row {
  margin-right: 16px;
}
.markdown-preview.markdown-preview .export-detail,
.markdown-preview.markdown-preview .export-item.active {
  color: var(--text-color) !important;
}
.markdown-preview.markdown-preview .export-item.active {
  color: var(--color-1) !important;
}
.markdown-preview.markdown-preview .export-detail,
.markdown-preview.markdown-preview .export-item.active,
.markdown-preview.markdown-preview .export-items-list-control {
  background: var(--bg-color2) !important;
}
.markdown-preview.markdown-preview #md-notification {
  top: -6px;
}
.markdown-preview.markdown-preview .md-show-hint::after {
  background: var(--color-1-0-b) !important;
  border-radius: 3px;
  box-shadow: 0 0 5px var(--color-1) !important;
}
.markdown-preview.markdown-preview .md-diagram-panel {
  width: 100% !important;
  max-width: 100% !important;
  border-radius: 8px;
  margin-top: 20px;
  border-color: var(--border-color) !important;
  background: var(--bg-color2) !important;
}
.markdown-preview.markdown-preview .md-diagram {
  width: 100% !important;
  max-width: 100% !important;
  margin-left: 0 !important;
  margin-right: 0 !important;
}
.markdown-preview.markdown-preview .md-diagram-panel > svg {
  width: 100% !important;
  max-width: 100% !important;
}
@media print {
  .markdown-preview.markdown-preview table,
  .markdown-preview.markdown-preview pre {
    page-break-inside: avoid;
  }
  .markdown-preview.markdown-preview pre {
    word-wrap: break-word;
  }
}
@media screen and (max-width: 768px) {
  .markdown-preview.markdown-preview #write {
    padding: 0 32px 32px;
  }
  .markdown-preview.markdown-preview h2.md-focus:before,
  .markdown-preview.markdown-preview h3.md-focus:before,
  .markdown-preview.markdown-preview h4.md-focus:before,
  .markdown-preview.markdown-preview h5.md-focus:before,
  .markdown-preview.markdown-preview h6.md-focus:before,
  .markdown-preview.markdown-preview h2:before,
  .markdown-preview.markdown-preview h3:before,
  .markdown-preview.markdown-preview h4:before,
  .markdown-preview.markdown-preview h5:before,
  .markdown-preview.markdown-preview h6:before {
    left: -30px;
  }
}
@media screen and (min-width: 769px) and (max-width: 1280px) {
  .markdown-preview.markdown-preview #write {
    padding: 0 64px 32px;
  }
}

      </style>
      <!-- The content below will be included at the end of the <head> element. --><script type="text/javascript">
  document.addEventListener("DOMContentLoaded", function () {
    // your code here
  });
</script></head><body for="html-export">
    
    
      <div class="crossnote markdown-preview  ">
      
<h1 id="c-高级编程学习笔记">C++ 高级编程学习笔记 </h1>
<ul>
<li><a href="#c-%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0">C++ 高级编程学习笔记</a>
<ul>
<li><a href="#1-c%E5%92%8C%E6%A0%87%E5%87%86%E5%BA%93%E9%80%9F%E6%88%90">1 C++和标准库速成</a>
<ul>
<li><a href="#11-c%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86">1.1 c++基础知识</a>
<ul>
<li><a href="#1111-%E4%B8%89%E5%90%91%E6%AF%94%E8%BE%83%E8%BF%90%E7%AE%97%E7%AC%A6">1.1.11 三向比较运算符</a></li>
<li><a href="#1113--%E5%B1%9E%E6%80%A7">1.1.13  属性</a>
<ul>
<li><a href="#nodiscard">nodiscard</a></li>
<li><a href="#maybe_unused">maybe_unused</a></li>
<li><a href="#noreturn">noreturn</a></li>
<li><a href="#deprecated">deprecated</a></li>
<li><a href="#likely-%E5%92%8C-unlikely">likely 和 unlikely</a></li>
</ul>
</li>
<li><a href="#1115-stdarray">1.1.15 std::array</a></li>
<li><a href="#1116-stdvector">1.1.16 std::vector</a></li>
<li><a href="#1117-stdpair">1.1.17 std::pair</a></li>
<li><a href="#1118-stdoptional">1.1.18 std::optional</a></li>
<li><a href="#1119-%E7%BB%93%E6%9E%84%E5%8C%96%E7%BB%91%E5%AE%9A">1.1.19 结构化绑定</a></li>
<li><a href="#1120-%E5%BE%AA%E7%8E%AF">1.1.20 循环</a></li>
<li><a href="#1121-%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8">1.1.21 初始化列表</a></li>
<li><a href="#1125-%E7%BB%9F%E4%B8%80%E5%88%9D%E5%A7%8B%E5%8C%96">1.1.25 统一初始化</a>
<ul>
<li><a href="#%E6%8C%87%E6%B4%BE%E5%88%9D%E5%A7%8B%E5%8C%96%E5%99%A8">指派初始化器</a></li>
</ul>
</li>
<li><a href="#1126-%E6%8C%87%E9%92%88%E5%92%8C%E5%8A%A8%E6%80%81%E5%86%85%E5%AD%98">1.1.26 指针和动态内存</a>
<ul>
<li><a href="#%E5%A0%86%E6%A0%88%E5%92%8C%E5%A0%86">堆栈和堆</a></li>
<li><a href="#%E4%BD%BF%E7%94%A8%E6%8C%87%E9%92%88">使用指针</a></li>
<li><a href="#%E5%8A%A8%E6%80%81%E5%88%86%E9%85%8D%E5%92%8C%E6%95%B0%E7%BB%84">动态分配和数组</a></li>
<li><a href="#%E7%A9%BA%E6%8C%87%E9%92%88%E5%B8%B8%E9%87%8F">空指针常量</a></li>
<li><a href="#%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88">智能指针</a></li>
</ul>
</li>
<li><a href="#1127-const-%E7%9A%84%E7%94%A8%E6%B3%95">1.1.27 const 的用法</a>
<ul>
<li><a href="#const-%E4%BF%AE%E9%A5%B0%E7%B1%BB%E5%9E%8B">const 修饰类型</a></li>
<li><a href="#const-%E6%96%B9%E6%B3%95">const 方法</a></li>
</ul>
</li>
<li><a href="#1128-constexpr-%E5%85%B3%E9%94%AE%E5%AD%97">1.1.28 constexpr 关键字</a></li>
<li><a href="#1129-consteval-%E5%85%B3%E9%94%AE%E5%AD%97">1.1.29 consteval 关键字</a></li>
<li><a href="#1130-%E5%BC%95%E7%94%A8">1.1.30 引用</a>
<ul>
<li><a href="#1-%E5%BC%95%E7%94%A8%E5%8F%98%E9%87%8F">1. 引用变量</a>
<ul>
<li><a href="#%E4%BF%AE%E6%94%B9%E5%BC%95%E7%94%A8">修改引用</a></li>
<li><a href="#const-%E5%BC%95%E7%94%A8">const 引用</a></li>
<li><a href="#%E6%8C%87%E9%92%88%E7%9A%84%E5%BC%95%E7%94%A8%E5%92%8C%E5%BC%95%E7%94%A8%E7%9A%84%E6%8C%87%E9%92%88">指针的引用和引用的指针</a></li>
<li><a href="#%E7%BB%93%E6%9E%84%E5%8C%96%E7%BB%91%E5%AE%9A%E5%92%8C%E5%BC%95%E7%94%A8">结构化绑定和引用</a></li>
</ul>
</li>
<li><a href="#2-%E5%BC%95%E7%94%A8%E6%95%B0%E6%8D%AE%E6%88%90%E5%91%98">2. 引用数据成员</a></li>
<li><a href="#3-%E5%BC%95%E7%94%A8%E4%BD%9C%E4%B8%BA%E5%87%BD%E6%95%B0%E5%8F%82%E6%95%B0">3. 引用作为函数参数</a>
<ul>
<li><a href="#const-%E5%BC%95%E7%94%A8%E4%BC%A0%E9%80%92">const 引用传递</a></li>
<li><a href="#%E5%80%BC%E4%BC%A0%E9%80%92%E5%92%8C%E5%BC%95%E7%94%A8%E4%BC%A0%E9%80%92">值传递和引用传递</a></li>
</ul>
</li>
<li><a href="#4-%E5%BC%95%E7%94%A8%E4%BD%9C%E4%B8%BA%E8%BF%94%E5%9B%9E%E5%80%BC">4. 引用作为返回值</a></li>
<li><a href="#5-%E5%9C%A8%E5%BC%95%E7%94%A8%E4%B8%8E%E6%8C%87%E9%92%88%E4%B9%8B%E9%97%B4%E6%8A%89%E6%8B%A9">5. 在引用与指针之间抉择</a></li>
</ul>
</li>
<li><a href="#1131-const_cast">1.1.31 const_cast()</a></li>
<li><a href="#1132-%E5%BC%82%E5%B8%B8">1.1.32 异常</a></li>
<li><a href="#1135-%E7%B1%BB%E5%9E%8B%E6%8E%A8%E6%96%AD">1.1.35 类型推断</a>
<ul>
<li><a href="#1-%E5%85%B3%E9%94%AE%E5%AD%97-auto">1. 关键字 auto</a></li>
<li><a href="#2-%E5%85%B3%E9%94%AE%E5%AD%97-decltype">2. 关键字 decltype</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><a href="#2-%E4%BD%BF%E7%94%A8-string-%E5%92%8C-string_view">2 使用 string 和 string_view</a>
<ul>
<li><a href="#21-%E5%8A%A8%E6%80%81%E5%AD%97%E7%AC%A6%E4%B8%B2">2.1 动态字符串</a>
<ul>
<li><a href="#212-%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%AD%97%E9%9D%A2%E9%87%8F">2.1.2 字符串字面量</a></li>
<li><a href="#213-c-stdstring-%E7%B1%BB">2.1.3 C++ std::string 类</a></li>
<li><a href="#214-stdstring_view-%E7%B1%BB">2.1.4 std::string_view 类</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#3-%E7%BC%96%E7%A0%81%E9%A3%8E%E6%A0%BC">3 编码风格</a>
<ul>
<li><a href="#32-%E4%B8%BA%E4%BB%A3%E7%A0%81%E7%BC%96%E5%86%99%E6%96%87%E6%A1%A3">3.2 为代码编写文档</a></li>
<li><a href="#33-%E5%88%86%E8%A7%A3">3.3 分解</a>
<ul>
<li><a href="#331-%E9%80%9A%E8%BF%87%E9%87%8D%E6%9E%84%E5%88%86%E8%A7%A3">3.3.1 通过重构分解</a></li>
<li><a href="#332-%E9%80%9A%E8%BF%87%E8%AE%BE%E8%AE%A1%E6%9D%A5%E5%88%86%E8%A7%A3">3.3.2 通过设计来分解</a></li>
</ul>
</li>
<li><a href="#34-%E5%91%BD%E5%90%8D">3.4 命名</a>
<ul>
<li><a href="#341-%E9%80%89%E6%8B%A9%E6%81%B0%E5%BD%93%E7%9A%84%E5%90%8D%E7%A7%B0">3.4.1 选择恰当的名称</a></li>
<li><a href="#342-%E5%91%BD%E5%90%8D%E7%BA%A6%E5%AE%9A">3.4.2 命名约定</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#6-%E8%AE%BE%E8%AE%A1%E5%8F%AF%E9%87%8D%E7%94%A8%E4%BB%A3%E7%A0%81">6 设计可重用代码</a>
<ul>
<li><a href="#61-%E9%87%8D%E7%94%A8%E5%93%B2%E5%AD%A6">6.1 重用哲学</a></li>
<li><a href="#62-%E5%A6%82%E4%BD%95%E8%AE%BE%E8%AE%A1%E5%8F%AF%E9%87%8D%E7%94%A8%E4%BB%A3%E7%A0%81">6.2 如何设计可重用代码</a>
<ul>
<li><a href="#621-%E4%BD%BF%E7%94%A8%E6%8A%BD%E8%B1%A1">6.2.1 使用抽象</a></li>
<li><a href="#622-%E6%9E%84%E5%BB%BA%E7%90%86%E6%83%B3%E7%9A%84%E9%87%8D%E7%94%A8%E4%BB%A3%E7%A0%81">6.2.2 构建理想的重用代码</a>
<ul>
<li><a href="#1-%E9%81%BF%E5%85%8D%E7%BB%84%E5%90%88%E4%B8%8D%E7%9B%B8%E5%B9%B2%E7%9A%84%E6%A6%82%E5%BF%B5%E6%88%96%E8%80%85%E9%80%BB%E8%BE%91%E4%B8%8A%E7%8B%AC%E7%AB%8B%E7%9A%84%E6%A6%82%E5%BF%B5">1. 避免组合不相干的概念或者逻辑上独立的概念</a></li>
<li><a href="#2-%E5%AF%B9%E6%B3%9B%E5%9E%8B%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95%E4%BD%BF%E7%94%A8%E6%A8%A1%E6%9D%BF">2. 对泛型数据结构和算法使用模板</a></li>
<li><a href="#3-%E6%8F%90%E4%BE%9B%E6%81%B0%E5%BD%93%E7%9A%84%E6%A3%80%E6%B5%8B%E5%92%8C%E5%AE%89%E5%85%A8%E6%8E%AA%E6%96%BD">3. 提供恰当的检测和安全措施</a></li>
<li><a href="#4-%E6%89%A9%E5%B1%95%E6%80%A7">4. 扩展性</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><a href="#7-%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86">7 内存管理</a>
<ul>
<li><a href="#71-%E4%BD%BF%E7%94%A8%E5%8A%A8%E6%80%81%E5%86%85%E5%AD%98">7.1 使用动态内存</a>
<ul>
<li><a href="#712-%E5%88%86%E9%85%8D%E4%B8%8E%E9%87%8A%E6%94%BE">7.1.2 分配与释放</a>
<ul>
<li><a href="#1-%E4%BD%BF%E7%94%A8-new-%E5%92%8C-delete">1. 使用 new 和 delete</a></li>
<li><a href="#2-%E5%85%B3%E4%BA%8E-malloc-%E5%87%BD%E6%95%B0">2. 关于 malloc() 函数</a></li>
<li><a href="#3-%E5%BD%93%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E5%A4%B1%E8%B4%A5%E6%97%B6">3. 当内存分配失败时</a></li>
</ul>
</li>
<li><a href="#713-%E6%95%B0%E7%BB%84">7.1.3 数组</a>
<ul>
<li><a href="#1-%E5%9F%BA%E6%9C%AC%E7%B1%BB%E5%9E%8B%E7%9A%84%E6%95%B0%E7%BB%84">1. 基本类型的数组</a></li>
<li><a href="#2-%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%95%B0%E7%BB%84">2. 对象的数组</a></li>
<li><a href="#3-%E5%88%A0%E9%99%A4%E6%95%B0%E7%BB%84">3. 删除数组</a></li>
<li><a href="#4-%E5%A4%9A%E7%BB%B4%E6%95%B0%E7%BB%84">4. 多维数组</a></li>
</ul>
</li>
<li><a href="#731-%E6%8C%87%E9%92%88%E8%BF%90%E7%AE%97">7.3.1 指针运算</a></li>
</ul>
</li>
<li><a href="#74-%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88">7.4 智能指针</a>
<ul>
<li><a href="#741-unique_ptr">7.4.1 unique_ptr</a>
<ul>
<li><a href="#1-%E5%88%9B%E5%BB%BA-unique_ptrs">1. 创建 unique_ptrs</a></li>
<li><a href="#2-%E4%BD%BF%E7%94%A8-unique_ptrs">2. 使用 unique_ptrs</a></li>
<li><a href="#3-unique_ptr-%E5%92%8C-c-%E9%A3%8E%E6%A0%BC%E6%95%B0%E7%BB%84">3. unique_ptr 和 C 风格数组</a></li>
<li><a href="#4-%E8%87%AA%E5%AE%9A%E4%B9%89-deleter">4. 自定义 deleter</a></li>
</ul>
</li>
<li><a href="#742-shared_ptr">7.4.2 shared_ptr</a>
<ul>
<li><a href="#1-%E5%BC%BA%E5%88%B6%E8%BD%AC%E6%8D%A2-shared_ptr">1. 强制转换 shared_ptr</a></li>
<li><a href="#2-%E5%BC%95%E7%94%A8%E8%AE%A1%E6%95%B0%E7%9A%84%E5%BF%85%E8%A6%81%E6%80%A7">2. 引用计数的必要性</a></li>
<li><a href="#3-%E5%88%AB%E5%90%8D">3. 别名</a></li>
</ul>
</li>
<li><a href="#743-weak_ptr">7.4.3 weak_ptr</a></li>
<li><a href="#744-%E7%A7%BB%E5%8A%A8%E8%AF%AD%E4%B9%89">7.4.4 移动语义</a></li>
<li><a href="#745-enable_shared_from_this">7.4.5 enable_shared_from_this</a></li>
</ul>
</li>
<li><a href="#75-%E5%B8%B8%E8%A7%81%E7%9A%84%E5%86%85%E5%AD%98%E9%99%B7%E9%98%B1">7.5 常见的内存陷阱</a>
<ul>
<li><a href="#751-%E5%88%86%E9%85%8D%E4%B8%8D%E8%B6%B3%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2">7.5.1 分配不足的字符串</a></li>
<li><a href="#752-%E8%AE%BF%E9%97%AE%E5%86%85%E5%AD%98%E8%B6%8A%E7%95%8C">7.5.2 访问内存越界</a></li>
<li><a href="#753-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F">7.5.3 内存泄漏</a></li>
<li><a href="#754-%E5%8F%8C%E9%87%8D%E5%88%A0%E9%99%A4%E5%92%8C%E6%97%A0%E6%95%88%E6%8C%87%E9%92%88">7.5.4 双重删除和无效指针</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#8-%E7%86%9F%E6%82%89%E7%B1%BB%E5%92%8C%E5%AF%B9%E8%B1%A1">8 熟悉类和对象</a>
<ul>
<li><a href="#82-%E7%BC%96%E5%86%99%E7%B1%BB">8.2 编写类</a>
<ul>
<li><a href="#821-%E7%B1%BB%E5%AE%9A%E4%B9%89">8.2.1 类定义</a>
<ul>
<li><a href="#1-%E7%B1%BB%E7%9A%84%E6%88%90%E5%91%98">1. 类的成员</a></li>
<li><a href="#2-%E8%AE%BF%E9%97%AE%E6%8E%A7%E5%88%B6">2. 访问控制</a></li>
<li><a href="#3-%E5%A3%B0%E6%98%8E%E9%A1%BA%E5%BA%8F">3. 声明顺序</a></li>
<li><a href="#4-%E7%B1%BB%E5%86%85%E6%88%90%E5%91%98%E5%88%9D%E5%A7%8B%E5%8C%96%E5%99%A8">4. 类内成员初始化器</a></li>
</ul>
</li>
<li><a href="#822-%E5%AE%9A%E4%B9%89%E6%96%B9%E6%B3%95">8.2.2 定义方法</a></li>
<li><a href="#823-%E4%BD%BF%E7%94%A8%E5%AF%B9%E8%B1%A1">8.2.3 使用对象</a></li>
</ul>
</li>
<li><a href="#83-%E5%AF%B9%E8%B1%A1%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F">8.3 对象的生命周期</a>
<ul>
<li><a href="#831-%E5%88%9B%E5%BB%BA%E5%AF%B9%E8%B1%A1">8.3.1 创建对象</a>
<ul>
<li><a href="#1-%E7%BC%96%E5%86%99%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0">1. 编写构造函数</a></li>
<li><a href="#2-%E4%BD%BF%E7%94%A8%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0">2. 使用构造函数</a></li>
<li><a href="#3-%E6%8F%90%E4%BE%9B%E5%A4%9A%E4%B8%AA%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0">3. 提供多个构造函数</a></li>
<li><a href="#4-%E9%BB%98%E8%AE%A4%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0">4. 默认构造函数</a></li>
<li><a href="#5-%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E5%88%9D%E5%A7%8B%E5%8C%96%E5%99%A8">5. 构造函数初始化器</a></li>
<li><a href="#6-%E5%A4%8D%E5%88%B6%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0">6. 复制构造函数</a></li>
<li><a href="#7-%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0">7. 初始化列表构造函数</a></li>
<li><a href="#8-%E5%A7%94%E6%89%98%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0">8. 委托构造函数</a></li>
<li><a href="#9-%E6%80%BB%E7%BB%93%E7%BC%96%E8%AF%91%E5%99%A8%E7%94%9F%E6%88%90%E7%9A%84%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0">9. 总结编译器生成的构造函数</a></li>
</ul>
</li>
<li><a href="#832-%E9%94%80%E6%AF%81%E5%AF%B9%E8%B1%A1">8.3.2 销毁对象</a></li>
<li><a href="#833-%E5%AF%B9%E8%B1%A1%E8%B5%8B%E5%80%BC">8.3.3 对象赋值</a>
<ul>
<li><a href="#1-%E5%A3%B0%E6%98%8E%E8%B5%8B%E5%80%BC%E8%BF%90%E7%AE%97%E7%AC%A6">1. 声明赋值运算符</a></li>
<li><a href="#2-%E5%AE%9A%E4%B9%89%E8%B5%8B%E5%80%BC%E8%BF%90%E7%AE%97%E7%AC%A6">2. 定义赋值运算符</a></li>
<li><a href="#3-%E6%98%BE%E5%BC%8F%E5%9C%B0%E9%BB%98%E8%AE%A4%E6%88%96%E5%88%A0%E9%99%A4%E8%B5%8B%E5%80%BC%E8%BF%90%E7%AE%97%E7%AC%A6">3. 显式地默认或删除赋值运算符</a></li>
</ul>
</li>
<li><a href="#835-%E5%A4%8D%E5%88%B6%E5%92%8C%E8%B5%8B%E5%80%BC%E7%9A%84%E5%8C%BA%E5%88%AB">8.3.5 复制和赋值的区别</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#9-%E7%B2%BE%E9%80%9A%E7%B1%BB%E4%B8%8E%E5%AF%B9%E8%B1%A1">9 精通类与对象</a>
<ul>
<li><a href="#91-%E5%8F%8B%E5%85%83">9.1 友元</a></li>
<li><a href="#92-%E5%AF%B9%E8%B1%A1%E4%B8%AD%E7%9A%84%E5%8A%A8%E6%80%81%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D">9.2 对象中的动态内存分配</a>
<ul>
<li><a href="#922-%E4%BD%BF%E7%94%A8%E6%9E%90%E6%9E%84%E5%87%BD%E6%95%B0%E9%87%8A%E6%94%BE%E5%86%85%E5%AD%98">9.2.2 使用析构函数释放内存</a></li>
<li><a href="#923-%E5%A4%84%E7%90%86%E5%A4%8D%E5%88%B6%E5%92%8C%E8%B5%8B%E5%80%BC">9.2.3 处理复制和赋值</a></li>
<li><a href="#924-%E4%BD%BF%E7%94%A8%E7%A7%BB%E5%8A%A8%E8%AF%AD%E4%B9%89%E5%A4%84%E7%90%86%E7%A7%BB%E5%8A%A8">9.2.4 使用移动语义处理移动</a>
<ul>
<li><a href="#1-%E5%8F%B3%E5%80%BC%E5%BC%95%E7%94%A8">1. 右值引用</a></li>
<li><a href="#2-%E5%AE%9E%E7%8E%B0%E7%A7%BB%E5%8A%A8%E8%AF%AD%E4%B9%89">2. 实现移动语义</a></li>
<li><a href="#3-%E6%B5%8B%E8%AF%95%E7%A7%BB%E5%8A%A8%E6%93%8D%E4%BD%9C">3. 测试移动操作</a></li>
<li><a href="#4-%E4%BD%BF%E7%94%A8%E7%A7%BB%E5%8A%A8%E8%AF%AD%E4%B9%89%E5%AE%9E%E7%8E%B0%E4%BA%A4%E6%8D%A2%E5%87%BD%E6%95%B0">4. 使用移动语义实现交换函数</a></li>
<li><a href="#5-%E5%9C%A8%E8%BF%94%E5%9B%9E%E8%AF%AD%E5%8F%A5%E4%B8%AD%E4%BD%BF%E7%94%A8-stdmove">5. 在返回语句中使用 std::move()</a></li>
<li><a href="#6-%E5%90%91%E5%87%BD%E6%95%B0%E4%BC%A0%E9%80%92%E5%8F%82%E6%95%B0%E7%9A%84%E6%9C%80%E4%BD%B3%E6%96%B9%E6%B3%95">6. 向函数传递参数的最佳方法</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#93-%E4%B8%8E%E6%96%B9%E6%B3%95%E6%9C%89%E5%85%B3%E7%9A%84%E6%9B%B4%E5%A4%9A%E5%86%85%E5%AE%B9">9.3 与方法有关的更多内容</a>
<ul>
<li><a href="#931-static-%E6%96%B9%E6%B3%95">9.3.1 static 方法</a></li>
<li><a href="#932-const%E6%96%B9%E6%B3%95">9.3.2 const方法</a>
<ul>
<li><a href="#mutable-%E6%95%B0%E6%8D%AE%E6%88%90%E5%91%98">mutable 数据成员</a></li>
</ul>
</li>
<li><a href="#933-%E6%96%B9%E6%B3%95%E9%87%8D%E8%BD%BD">9.3.3 方法重载</a>
<ul>
<li><a href="#1-%E5%9F%BA%E4%BA%8E-const-%E7%9A%84%E9%87%8D%E8%BD%BD">1. 基于 const 的重载</a></li>
<li><a href="#2-%E6%98%BE%E5%BC%8F%E5%88%A0%E9%99%A4%E9%87%8D%E8%BD%BD">2. 显式删除重载</a></li>
<li><a href="#3-%E5%BC%95%E7%94%A8%E9%99%90%E5%AE%9A%E6%96%B9%E6%B3%95">3. 引用限定方法</a></li>
</ul>
</li>
<li><a href="#934-%E5%86%85%E8%81%94%E6%96%B9%E6%B3%95">9.3.4 内联方法</a></li>
<li><a href="#935-%E9%BB%98%E8%AE%A4%E5%8F%82%E6%95%B0">9.3.5 默认参数</a></li>
</ul>
</li>
<li><a href="#94-%E4%B8%8D%E5%90%8C%E7%9A%84%E6%95%B0%E6%8D%AE%E6%88%90%E5%91%98%E7%B1%BB%E5%9E%8B">9.4 不同的数据成员类型</a>
<ul>
<li><a href="#941-%E9%9D%99%E6%80%81%E6%95%B0%E6%8D%AE%E6%88%90%E5%91%98">9.4.1 静态数据成员</a></li>
<li><a href="#942-const-static-%E6%95%B0%E6%8D%AE%E6%88%90%E5%91%98">9.4.2 const static 数据成员</a></li>
<li><a href="#943-%E5%BC%95%E7%94%A8%E6%95%B0%E6%8D%AE%E6%88%90%E5%91%98">9.4.3 引用数据成员</a></li>
</ul>
</li>
<li><a href="#95-%E5%B5%8C%E5%A5%97%E7%B1%BB">9.5 嵌套类</a></li>
<li><a href="#96-%E7%B1%BB%E5%86%85%E7%9A%84%E6%9E%9A%E4%B8%BE%E7%B1%BB%E5%9E%8B">9.6 类内的枚举类型</a></li>
<li><a href="#97-%E8%BF%90%E7%AE%97%E7%AC%A6%E9%87%8D%E8%BD%BD">9.7 运算符重载</a>
<ul>
<li><a href="#971-%E7%A4%BA%E4%BE%8B%E4%B8%BA-spreadsheetcell-%E5%AE%9E%E7%8E%B0%E5%8A%A0%E6%B3%95">9.7.1 示例：为 SpreadsheetCell 实现加法</a>
<ul>
<li><a href="#1-%E9%A6%96%E6%AC%A1%E5%B0%9D%E8%AF%95add-%E5%8A%A0%E6%B3%95">1. 首次尝试：add 加法</a></li>
<li><a href="#2-%E7%AC%AC%E4%BA%8C%E6%AC%A1%E5%B0%9D%E8%AF%95%E5%B0%86-operator-%E4%BD%9C%E4%B8%BA%E6%96%B9%E6%B3%95%E9%87%8D%E8%BD%BD">2. 第二次尝试：将 operator+ 作为方法重载</a>
<ul>
<li><a href="#%E9%9A%90%E5%BC%8F%E8%BD%AC%E6%8D%A2">隐式转换</a></li>
</ul>
</li>
<li><a href="#3-%E7%AC%AC%E4%B8%89%E6%AC%A1%E5%B0%9D%E8%AF%95%E5%85%A8%E5%B1%80-operator">3. 第三次尝试：全局 operator+</a></li>
</ul>
</li>
<li><a href="#972-%E9%87%8D%E8%BD%BD%E7%AE%97%E6%9C%AF%E8%BF%90%E7%AE%97%E7%AC%A6">9.7.2 重载算术运算符</a>
<ul>
<li><a href="#%E9%87%8D%E8%BD%BD%E7%AE%80%E5%86%99%E7%AE%97%E6%9C%AF%E8%BF%90%E7%AE%97%E7%AC%A6">重载简写算术运算符</a></li>
</ul>
</li>
<li><a href="#973-%E9%87%8D%E8%BD%BD%E6%AF%94%E8%BE%83%E8%BF%90%E7%AE%97%E7%AC%A6">9.7.3 重载比较运算符</a></li>
<li><a href="#974-%E5%88%9B%E5%BB%BA%E5%85%B7%E6%9C%89%E8%BF%90%E7%AE%97%E7%AC%A6%E9%87%8D%E8%BD%BD%E7%9A%84%E7%B1%BB%E5%9E%8B">9.7.4 创建具有运算符重载的类型</a></li>
</ul>
</li>
<li><a href="#98-%E5%88%9B%E5%BB%BA%E7%A8%B3%E5%AE%9A%E7%9A%84%E6%8E%A5%E5%8F%A3">9.8 创建稳定的接口</a>
<ul>
<li><a href="#%E4%BD%BF%E7%94%A8%E6%8E%A5%E5%8F%A3%E7%B1%BB%E5%92%8C%E5%AE%9E%E7%8E%B0%E7%B1%BB">使用接口类和实现类</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#10-%E6%8F%AD%E7%A7%98%E7%BB%A7%E6%89%BF%E6%8A%80%E6%9C%AF">10 揭秘继承技术</a>
<ul>
<li><a href="#101-%E4%BD%BF%E7%94%A8%E7%BB%A7%E6%89%BF%E6%9E%84%E5%BB%BA%E7%B1%BB">10.1 使用继承构建类</a>
<ul>
<li><a href="#1012-%E9%87%8D%E5%86%99%E6%96%B9%E6%B3%95">10.1.2 重写方法</a>
<ul>
<li><a href="#%E5%85%B3%E9%94%AE%E5%AD%97-virtual">关键字 virtual</a></li>
<li><a href="#%E9%87%8D%E5%86%99%E6%96%B9%E6%B3%95%E7%9A%84%E8%AF%AD%E6%B3%95">重写方法的语法</a></li>
<li><a href="#%E5%AE%A2%E6%88%B7%E5%AF%B9%E9%87%8D%E5%86%99%E6%96%B9%E6%B3%95%E7%9A%84%E7%9C%8B%E6%B3%95">客户对重写方法的看法</a></li>
<li><a href="#override-%E5%85%B3%E9%94%AE%E5%AD%97">override 关键字</a></li>
<li><a href="#virtual-%E7%9A%84%E7%9C%9F%E7%9B%B8">virtual 的真相</a>
<ul>
<li><a href="#%E9%9A%90%E8%97%8F%E8%80%8C%E4%B8%8D%E6%98%AF%E9%87%8D%E5%86%99">隐藏而不是重写</a></li>
<li><a href="#%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0-virtual">如何实现 virtual</a></li>
<li><a href="#%E4%BD%BF%E7%94%A8-virtual-%E7%9A%84%E7%90%86%E7%94%B1">使用 virtual 的理由</a></li>
</ul>
</li>
<li><a href="#%E7%A6%81%E7%94%A8%E9%87%8D%E5%86%99">禁用重写</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#103-%E5%88%A9%E7%94%A8%E7%88%B6%E7%B1%BB">10.3 利用父类</a>
<ul>
<li><a href="#1031-%E7%88%B6%E7%B1%BB%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0">10.3.1 父类构造函数</a></li>
<li><a href="#1032-%E7%88%B6%E7%B1%BB%E7%9A%84%E6%9E%90%E6%9E%84%E5%87%BD%E6%95%B0">10.3.2 父类的析构函数</a></li>
<li><a href="#1033-%E4%BD%BF%E7%94%A8%E7%88%B6%E7%B1%BB%E6%96%B9%E6%B3%95">10.3.3 使用父类方法</a></li>
<li><a href="#1034-%E5%90%91%E4%B8%8A%E8%BD%AC%E5%9E%8B%E5%92%8C%E5%90%91%E4%B8%8B%E8%BD%AC%E5%9E%8B">10.3.4 向上转型和向下转型</a></li>
</ul>
</li>
<li><a href="#104-%E7%BB%A7%E6%89%BF%E4%B8%8E%E5%A4%9A%E6%80%81%E6%80%A7">10.4 继承与多态性</a>
<ul>
<li><a href="#1042-%E8%AE%BE%E8%AE%A1%E5%A4%9A%E6%80%81%E6%80%A7%E7%9A%84%E7%94%B5%E5%AD%90%E8%A1%A8%E6%A0%BC%E5%8D%95%E5%85%83%E6%A0%BC">10.4.2 设计多态性的电子表格单元格</a></li>
<li><a href="#1043-spreadsheetcell-%E5%9F%BA%E7%B1%BB">10.4.3 SpreadsheetCell 基类</a>
<ul>
<li><a href="#%E5%88%9D%E6%AC%A1%E5%B0%9D%E8%AF%95">初次尝试</a></li>
<li><a href="#%E7%BA%AF%E8%99%9A%E6%96%B9%E6%B3%95%E5%92%8C%E6%8A%BD%E8%B1%A1%E5%9F%BA%E7%B1%BB">纯虚方法和抽象基类</a></li>
</ul>
</li>
<li><a href="#1044-%E7%8B%AC%E7%AB%8B%E7%9A%84%E6%B4%BE%E7%94%9F%E7%B1%BB">10.4.4 独立的派生类</a>
<ul>
<li><a href="#stringspreadsheetcell-%E7%B1%BB%E7%9A%84%E5%AE%9A%E4%B9%89">StringSpreadsheetCell 类的定义</a></li>
<li><a href="#stringspreadsheetcell-%E7%9A%84%E5%AE%9E%E7%8E%B0">StringSpreadsheetCell 的实现</a></li>
<li><a href="#doublespreadsheetcell-%E7%B1%BB%E7%9A%84%E5%AE%9A%E4%B9%89%E5%92%8C%E5%AE%9E%E7%8E%B0">DoubleSpreadsheetCell 类的定义和实现</a></li>
</ul>
</li>
<li><a href="#1045-%E5%88%A9%E7%94%A8%E5%A4%9A%E6%80%81%E6%80%A7">10.4.5 利用多态性</a></li>
<li><a href="#1045-%E8%80%83%E8%99%91%E5%B0%86%E6%9D%A5">10.4.5 考虑将来</a></li>
</ul>
</li>
<li><a href="#105-%E5%A4%9A%E9%87%8D%E7%BB%A7%E6%89%BF">10.5 多重继承</a>
<ul>
<li><a href="#1051-%E4%BB%8E%E5%A4%9A%E4%B8%AA%E7%B1%BB%E7%BB%A7%E6%89%BF">10.5.1 从多个类继承</a></li>
<li><a href="#1052-%E5%90%8D%E7%A7%B0%E5%86%B2%E7%AA%81%E5%92%8C%E6%AD%A7%E4%B9%89%E5%9F%BA%E7%B1%BB">10.5.2 名称冲突和歧义基类</a>
<ul>
<li><a href="#%E5%90%8D%E7%A7%B0%E6%AD%A7%E4%B9%89">名称歧义</a></li>
<li><a href="#%E6%AD%A7%E4%B9%89%E5%9F%BA%E7%B1%BB">歧义基类</a></li>
<li><a href="#%E5%A4%9A%E9%87%8D%E7%BB%A7%E6%89%BF%E7%9A%84%E7%94%A8%E9%80%94">多重继承的用途</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#106-%E6%9C%89%E8%B6%A3%E8%80%8C%E6%99%A6%E6%B6%A9%E7%9A%84%E7%BB%A7%E6%89%BF%E9%97%AE%E9%A2%98">10.6 有趣而晦涩的继承问题</a>
<ul>
<li><a href="#1061-%E4%BF%AE%E6%94%B9%E9%87%8D%E5%86%99%E6%96%B9%E6%B3%95%E7%9A%84%E8%BF%94%E5%9B%9E%E7%B1%BB%E5%9E%8B">10.6.1 修改重写方法的返回类型</a></li>
<li><a href="#1062-%E6%B4%BE%E7%94%9F%E7%B1%BB%E4%B8%AD%E6%B7%BB%E5%8A%A0%E8%99%9A%E5%9F%BA%E7%B1%BB%E6%96%B9%E6%B3%95%E7%9A%84%E9%87%8D%E8%BD%BD">10.6.2 派生类中添加虚基类方法的重载</a></li>
<li><a href="#1063-%E7%BB%A7%E6%89%BF%E7%9A%84%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0">10.6.3 继承的构造函数</a>
<ul>
<li><a href="#%E9%9A%90%E8%97%8F%E7%BB%A7%E6%89%BF%E7%9A%84%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0">隐藏继承的构造函数</a></li>
<li><a href="#%E7%BB%A7%E6%89%BF%E7%9A%84%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E5%92%8C%E5%A4%9A%E9%87%8D%E7%BB%A7%E6%89%BF">继承的构造函数和多重继承</a></li>
<li><a href="#%E6%95%B0%E6%8D%AE%E6%88%90%E5%91%98%E7%9A%84%E5%88%9D%E5%A7%8B%E5%8C%96">数据成员的初始化</a></li>
</ul>
</li>
<li><a href="#1064-%E9%87%8D%E5%86%99%E6%97%B6%E7%9A%84%E7%89%B9%E6%AE%8A%E6%83%85%E5%86%B5">10.6.4 重写时的特殊情况</a>
<ul>
<li><a href="#%E9%9D%99%E6%80%81%E5%9F%BA%E7%B1%BB%E6%96%B9%E6%B3%95">静态基类方法</a></li>
<li><a href="#%E9%87%8D%E8%BD%BD%E5%9F%BA%E7%B1%BB%E6%96%B9%E6%B3%95">重载基类方法</a></li>
<li><a href="#%E5%9F%BA%E7%B1%BB%E7%9A%84-private-%E6%96%B9%E6%B3%95">基类的 private 方法</a></li>
<li><a href="#%E5%9F%BA%E7%B1%BB%E6%96%B9%E6%B3%95%E5%85%B7%E6%9C%89%E9%BB%98%E8%AE%A4%E5%8F%82%E6%95%B0">基类方法具有默认参数</a></li>
<li><a href="#%E5%9F%BA%E7%B1%BB%E6%96%B9%E6%B3%95%E5%85%B7%E6%9C%89%E4%B8%8D%E5%90%8C%E7%9A%84%E8%AE%BF%E9%97%AE%E7%BA%A7%E5%88%AB">基类方法具有不同的访问级别</a></li>
</ul>
</li>
<li><a href="#1065-%E6%B4%BE%E7%94%9F%E7%B1%BB%E4%B8%AD%E7%9A%84%E5%A4%8D%E5%88%B6%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E5%92%8C%E8%B5%8B%E5%80%BC%E8%BF%90%E7%AE%97%E7%AC%A6">10.6.5 派生类中的复制构造函数和赋值运算符</a></li>
<li><a href="#1066-%E8%BF%90%E8%A1%8C%E6%9C%9F%E7%B1%BB%E5%9E%8B%E5%B7%A5%E5%85%B7">10.6.6 运行期类型工具</a></li>
<li><a href="#1067-%E9%9D%9E-public-%E7%BB%A7%E6%89%BF">10.6.7 非 public 继承</a></li>
<li><a href="#1068-%E8%99%9A%E5%9F%BA%E7%B1%BB">10.6.8 虚基类</a></li>
</ul>
</li>
<li><a href="#107-%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2">10.7 类型转换</a>
<ul>
<li><a href="#1071-static_cast">10.7.1 static_cast()</a></li>
<li><a href="#1072-reinterpret_cast">10.7.2 reinterpret_cast()</a></li>
<li><a href="#1073-stdbit_cast">10.7.3 std::bit_cast()</a></li>
<li><a href="#1074-dynamic_cast">10.7.4 dynamic_cast()</a></li>
<li><a href="#1075-%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2%E5%B0%8F%E7%BB%93">10.7.5 类型转换小结</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#11-%E9%9B%B6%E7%A2%8E%E7%9A%84%E5%B7%A5%E4%BD%9C">11 零碎的工作</a>
<ul>
<li><a href="#111-%E6%A8%A1%E5%9D%97">11.1 模块</a>
<ul>
<li><a href="#1111-%E6%A8%A1%E5%9D%97%E6%8E%A5%E5%8F%A3%E6%96%87%E4%BB%B6">11.1.1 模块接口文件</a></li>
<li><a href="#1112-%E6%A8%A1%E5%9D%97%E5%AE%9E%E7%8E%B0%E6%96%87%E4%BB%B6">11.1.2 模块实现文件</a></li>
<li><a href="#1113-%E4%BB%8E%E5%AE%9E%E7%8E%B0%E4%B8%AD%E5%88%86%E7%A6%BB%E6%8E%A5%E5%8F%A3">11.1.3 从实现中分离接口</a></li>
<li><a href="#%E5%8F%AF%E8%A7%81%E6%80%A7%E5%92%8C%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7">可见性和可访问性</a></li>
<li><a href="#1115-%E5%AD%90%E6%A8%A1%E5%9D%97">11.1.5 子模块</a></li>
<li><a href="#1116-%E6%A8%A1%E5%9D%97%E5%88%92%E5%88%86">11.1.6 模块划分</a>
<ul>
<li><a href="#%E5%AE%9E%E7%8E%B0%E5%88%86%E5%8C%BA">实现分区</a></li>
</ul>
</li>
<li><a href="#1117-%E5%A4%B4%E6%96%87%E4%BB%B6%E5%8D%95%E5%85%83">11.1.7 头文件单元</a></li>
</ul>
</li>
<li><a href="#112-%E5%A4%B4%E6%96%87%E4%BB%B6">11.2 头文件</a>
<ul>
<li><a href="#1121-%E9%87%8D%E5%A4%8D%E5%AE%9A%E4%B9%89">11.21 重复定义</a></li>
<li><a href="#1122-%E5%BE%AA%E7%8E%AF%E4%BE%9D%E8%B5%96">11.2.2 循环依赖</a></li>
<li><a href="#1123-%E6%9F%A5%E8%AF%A2%E5%A4%B4%E6%96%87%E4%BB%B6%E6%98%AF%E5%90%A6%E5%AD%98%E5%9C%A8">11.2.3 查询头文件是否存在</a></li>
</ul>
</li>
<li><a href="#113-%E6%A0%B8%E5%BF%83%E8%AF%AD%E8%A8%80%E7%89%B9%E6%80%A7%E7%9A%84%E7%89%B9%E6%80%A7%E6%B5%8B%E8%AF%95%E5%AE%8F">11.3 核心语言特性的特性测试宏</a></li>
<li><a href="#114-static-%E5%85%B3%E9%94%AE%E5%AD%97">11.4 static 关键字</a>
<ul>
<li><a href="#1141-%E9%9D%99%E6%80%81%E6%95%B0%E6%8D%AE%E6%88%90%E5%91%98%E5%92%8C%E6%96%B9%E6%B3%95">11.4.1 静态数据成员和方法</a></li>
<li><a href="#1142-%E9%9D%99%E6%80%81%E9%93%BE%E6%8E%A5">11.4.2 静态链接</a></li>
<li><a href="#1143-%E5%87%BD%E6%95%B0%E4%B8%AD%E7%9A%84%E9%9D%99%E6%80%81%E5%8F%98%E9%87%8F">11.4.3 函数中的静态变量</a></li>
<li><a href="#1144-%E9%9D%9E%E5%B1%80%E9%83%A8%E5%8F%98%E9%87%8F%E7%9A%84%E5%88%9D%E5%A7%8B%E5%8C%96%E9%A1%BA%E5%BA%8F">11.4.4 非局部变量的初始化顺序</a></li>
<li><a href="#1145-%E9%9D%9E%E5%B1%80%E9%83%A8%E5%8F%98%E9%87%8F%E7%9A%84%E9%94%80%E6%AF%81%E9%A1%BA%E5%BA%8F">11.4.5 非局部变量的销毁顺序</a></li>
</ul>
</li>
<li><a href="#115-c-%E7%9A%84%E5%AE%9E%E7%94%A8%E5%B7%A5%E5%85%B7">11.5 C 的实用工具</a>
<ul>
<li><a href="#1151-%E8%BE%B9%E9%95%BF%E5%8F%82%E6%95%B0%E5%88%97%E8%A1%A8">11.5.1 边长参数列表</a>
<ul>
<li><a href="#%E8%AE%BF%E9%97%AE%E5%8F%82%E6%95%B0">访问参数</a></li>
<li><a href="#%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E5%BA%94%E8%AF%A5%E4%BD%BF%E7%94%A8-c-%E9%A3%8E%E6%A0%BC%E7%9A%84%E8%BE%B9%E9%95%BF%E5%8F%82%E6%95%B0%E5%88%97%E8%A1%A8">为什么不应该使用 C 风格的边长参数列表</a></li>
</ul>
</li>
<li><a href="#1152-%E9%A2%84%E5%A4%84%E7%90%86%E5%99%A8%E5%AE%8F">11.5.2 预处理器宏</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#12-%E5%88%A9%E7%94%A8%E6%A8%A1%E6%9D%BF%E7%BC%96%E5%86%99%E6%B3%9B%E5%9E%8B%E4%BB%A3%E7%A0%81">12 利用模板编写泛型代码</a>
<ul>
<li><a href="#121-%E6%A8%A1%E6%9D%BF%E6%A6%82%E8%BF%B0">12.1 模板概述</a></li>
<li><a href="#122-%E7%B1%BB%E6%A8%A1%E6%9D%BF">12.2 类模板</a>
<ul>
<li><a href="#1221-%E7%BC%96%E5%86%99%E7%B1%BB%E6%A8%A1%E6%9D%BF">12.2.1 编写类模板</a>
<ul>
<li><a href="#%E7%BC%96%E5%86%99%E4%B8%8D%E4%BD%BF%E7%94%A8%E6%A8%A1%E6%9D%BF%E7%9A%84%E4%BB%A3%E7%A0%81">编写不使用模板的代码</a></li>
<li><a href="#%E6%A8%A1%E6%9D%BF-grid-%E7%B1%BB">模板 Grid 类</a>
<ul>
<li><a href="#grid-%E7%B1%BB%E5%AE%9A%E4%B9%89">Grid 类定义</a></li>
<li><a href="#grid-%E7%B1%BB%E7%9A%84%E6%96%B9%E6%B3%95%E5%AE%9A%E4%B9%89">Grid 类的方法定义</a></li>
</ul>
</li>
<li><a href="#%E4%BD%BF%E7%94%A8-grid-%E6%A8%A1%E6%9D%BF">使用 Grid 模板</a></li>
</ul>
</li>
<li><a href="#1222-%E7%BC%96%E8%AF%91%E5%99%A8%E5%A4%84%E7%90%86%E6%A8%A1%E6%9D%BF%E7%9A%84%E5%8E%9F%E7%90%86">12.2.2 编译器处理模板的原理</a>
<ul>
<li><a href="#1-%E9%80%89%E6%8B%A9%E6%80%A7%E5%AE%9E%E4%BE%8B%E5%8C%96">1. 选择性实例化</a></li>
<li><a href="#2-%E6%A8%A1%E6%9D%BF%E5%AF%B9%E7%B1%BB%E5%9E%8B%E7%9A%84%E8%A6%81%E6%B1%82">2. 模板对类型的要求</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><a href="#16-c%E6%A0%87%E5%87%86%E5%BA%93%E6%A6%82%E8%BF%B0">16 C++标准库概述</a>
<ul>
<li><a href="#162-c%E6%A0%87%E5%87%86%E5%BA%93%E6%A6%82%E8%BF%B0">16.2 C++标准库概述</a>
<ul>
<li><a href="#1621-%E5%AD%97%E7%AC%A6%E4%B8%B2">16.2.1 字符串</a></li>
<li><a href="#1622-%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F">16.2.2 正则表达式</a></li>
<li><a href="#1623-io%E6%B5%81">16.2.3 I/O流</a></li>
<li><a href="#1624-%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88">16.2.4 智能指针</a></li>
<li><a href="#1626-%E6%95%B0%E5%AD%A6%E5%B7%A5%E5%85%B7">16.2.6 数学工具</a></li>
<li><a href="#16212-%E5%87%BD%E6%95%B0%E5%AF%B9%E8%B1%A1">16.2.12 函数对象</a></li>
<li><a href="#16213-%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F">16.2.13 文件系统</a></li>
<li><a href="#16216-%E6%A0%87%E5%87%86%E6%95%B4%E6%95%B0%E7%B1%BB%E5%9E%8B">16.2.16 标准整数类型</a></li>
<li><a href="#16217-%E5%AE%B9%E5%99%A8">16.2.17 容器</a></li>
<li><a href="#16218-%E7%AE%97%E6%B3%95">16.2.18 算法</a>
<ul>
<li><a href="#%E9%9D%9E%E4%BF%AE%E6%94%B9%E9%A1%BA%E5%BA%8F%E7%AE%97%E6%B3%95">非修改顺序算法</a>
<ul>
<li><a href="#%E6%90%9C%E7%B4%A2%E7%AE%97%E6%B3%95">搜索算法</a></li>
<li><a href="#%E6%AF%94%E8%BE%83%E7%AE%97%E6%B3%95">比较算法</a></li>
<li><a href="#%E8%AE%A1%E6%95%B0%E7%AE%97%E6%B3%95">计数算法</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<div style="page-break-after: always;"></div>
<h2 id="1-c和标准库速成">1 C++和标准库速成 </h2>
<h3 id="11-c基础知识">1.1 c++基础知识 </h3>
<h4 id="1111-三向比较运算符">1.1.11 三向比较运算符 </h4>
<p>三向比较运算符： <em><strong>&lt;=&gt;</strong></em></p>
<p>返回类枚举类型，定义在 <em><strong>&lt;compare&gt;</strong></em> 和 std 名称空间中。如果操作数是整数类型，则结果时所谓的强排序，并且可以是以下之一。</p>
<ul>
<li><em><strong>strong_ordering</strong></em>::less  不用解释啦</li>
<li>strong_ordering::greater</li>
<li>strong_ordering::equal</li>
</ul>
<p>如果操作数是浮点类型，结果是一个偏序。</p>
<ul>
<li><em><strong>partial_ordering</strong></em>::less</li>
<li>partial_ordering::greater</li>
<li>partial_ordering::equivalent</li>
<li>partial_ordering::unordered：如果有一个操作数是非数字或者两个操作数都是非数字</li>
</ul>
<p>以下是它的用法的示例：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-int">int</span> i<span class="token punctuation">{</span><span class="token number">11</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

strong_ordering result<span class="token punctuation">{</span>i <span class="token operator">&lt;=&gt;</span> <span class="token number">0</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>result <span class="token operator">==</span> strong_ordering<span class="token double-colon punctuation">::</span>less<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    cout <span class="token operator">&lt;&lt;</span> <span class="token string">"less"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>result <span class="token operator">==</span> strong_ordering<span class="token double-colon punctuation">::</span>greater<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    cout <span class="token operator">&lt;&lt;</span> <span class="token string">"greater"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>result <span class="token operator">==</span> strong_ordering<span class="token double-colon punctuation">::</span>equal<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    cout <span class="token operator">&lt;&lt;</span> <span class="token string">"equal"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>还有一种弱排序，这是可以选择的另一种排序类型，以针对你自己的类型实现三向比较。</p>
<ul>
<li><em><strong>weak_ordering</strong></em>::less</li>
<li>weak_ordering::greater</li>
<li>weak_ordering::equivalent</li>
</ul>
<p>最后，&lt;compare&gt; 提供命名的比较函数来解释排序结果。这些函数是 std::is_eq()、is_neq()、is_lt()、is_lteq() 以及 is_gteq()。</p>
<h4 id="1113–属性">1.1.13  属性 </h4>
<p>属性是一种将可选的和/或特定于编译器产商的信息添加到源代码中的机制。在 C++ 对属性进行标准化之前，编译器缠上决定了如何指定此类信息，例如 __attribute__ 和__declspec 等。从 C++11 开始，通过使用双方括号 [[attribute]] 对属性进行标准化的支持。</p>
<p>C++ 标准定义了几个在函数上下文中有用的标准属性。</p>
<h5 id="nodiscard">nodiscard </h5>
<p>[[nodiscard]] 属性可用于有一个返回值的函数，以使编译器在该函数被调用却没有对返回的值进行任何处理时发出警告。以下是一个例子。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token punctuation">[</span><span class="token punctuation">[</span>nodiscard<span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token keyword keyword-int">int</span> <span class="token function">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> <span class="token number">42</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword keyword-int">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token function">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>编译器将会发出警告。</p>
<p>例如，此特性可用于返回错误代码的函数。通过将 [[nodiscard]] 属性添加到此类函数中，卒后无代码就无法被忽视。</p>
<p>更笼统地说，[[nodiscard]] 属性可用于类、函数和枚举。</p>
<p>从 C++20 开始，可以字符串形式为 [[nodiscard]] 属性提供一个原因，例如：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token punctuation">[</span><span class="token punctuation">[</span><span class="token function">nodiscard</span><span class="token punctuation">(</span><span class="token string">"Some explanation"</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token keyword keyword-int">int</span> <span class="token function">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><h5 id="maybe_unused">maybe_unused </h5>
<p>[[maybe_unused]] 属性可用于禁止编译器在未使用某些内容时发出警告，如下例所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-int">int</span> <span class="token function">func</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> param1<span class="token punctuation">,</span> <span class="token keyword keyword-int">int</span> param2<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> <span class="token number">42</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>如果将编译器警告级别设置的足够高，则此函数定义将导致两个编译器警告。</p>
<p>通过使用 [[maybe_unused]]，可以阻止此类警告。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-int">int</span> <span class="token function">func</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> param1<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">[</span>maybe_unused<span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token keyword keyword-int">int</span> param2<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> <span class="token number">42</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>在这种情况下，第二个参数标记有禁止其警告的属性。现在，编译器仅对 param1 发出警告。</p>
<p>[[maybe_unused]] 属性可用于类和结构体、非静态数据成员、联合、typedef、类型别名、变量、函数、枚举以及枚举值。</p>
<h5 id="noreturn">noreturn </h5>
<p>向函数添加 [[noreturn]] 属性意味着它永远不会将控制权返回给调用点。通常，函数要么导致某种终止（进程终止或线程终止），要么引发异常。使用此属性，编译器可以避免发出某些警告或错误，因为它现在可以更多地来哦姐该函数的用途。</p>
<h5 id="deprecated">deprecated </h5>
<p>[[deprecated]]可用于将某些内容标记为以弃用，这意味着仍可以使用它，但不鼓励使用。此属性接受一个可选参数，该参数可用于解释不赞成使用的原因。如以下示例所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token punctuation">[</span><span class="token punctuation">[</span><span class="token function">deprecated</span><span class="token punctuation">(</span><span class="token string">"Unsafe method, please use xyz"</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token keyword keyword-void">void</span> <span class="token function">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>如果使用了已弃用的函数，将会收到编译错误或警告。</p>
<h5 id="likely-和-unlikely">likely 和 unlikely </h5>
<p>这个不需要知道了</p>
<h4 id="1115-stdarray">1.1.15 std::array </h4>
<p>C++ 有一种固定大小的特殊容器 std::array，这种容器在 <array> 头文件中定义。它基本上是对 C 风格的数组进行了简单包装。</array></p>
<p>用 std::array 替代 C 风格的数组会带来很多好处。它总是知道自身大小；不会自动转换为指针，从而避免了某些类型的 bug；具有迭代器，可方便地遍历元素。详见第 17章迭代器。<br>
array 容器的用法如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>array<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token operator">&gt;</span> arr<span class="token punctuation">{</span><span class="token number">9</span><span class="token punctuation">,</span> <span class="token number">8</span><span class="token punctuation">,</span> <span class="token number">7</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
cout <span class="token operator">&lt;&lt;</span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"Array size = {}"</span><span class="token punctuation">,</span> arr<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">)</span><span class="token punctuation">;</span>
cout <span class="token operator">&lt;&lt;</span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"2nd element = {}"</span><span class="token punctuation">,</span> arr<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
</code></pre><p>C++ 支持所谓的类模板参数推导（CTAD），详见第 12 章。这可以避免为某些类模板指定尖括号之间的模板类型。CTAD 仅在初始化时起作用，因为编译器使用此初始化自动推导模板类型。这适用于 std::array，如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>array arr<span class="token punctuation">{</span><span class="token number">9</span><span class="token punctuation">,</span> <span class="token number">8</span><span class="token punctuation">,</span> <span class="token number">7</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><h4 id="1116-stdvector">1.1.16 std::vector </h4>
<p>标准库提供了多个不同的非固定大小容器，可用于存储信息。std::vector 就是此类容器的一个示例，它在 &lt;vector&gt; 声明，用一种更灵活和安全的机制取代 C风格数组的概念。用户不需要担心内存的管理，因为 vector 将自动分配足够的内存来存放其元素。vector 是动态的，意味着可在运行时添加和删除元素。容器详见第 18 章。下面演示 vector 的基本功能。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>vector<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span> myVector<span class="token punctuation">{</span><span class="token number">11</span><span class="token punctuation">,</span> <span class="token number">22</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

myVector<span class="token punctuation">.</span><span class="token function">push_back</span><span class="token punctuation">(</span><span class="token number">33</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
myVector<span class="token punctuation">.</span><span class="token function">push_back</span><span class="token punctuation">(</span><span class="token number">44</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

cout <span class="token operator">&lt;&lt;</span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"lst element: {}"</span><span class="token punctuation">,</span> myVector<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
</code></pre><p>myVector 被声明为 vector&lt;int&gt;。vector 是一个泛型容器，几乎可容纳任何类型的对象，但是 vector 中的所有元素不许是同一类型，在尖括号内指定这个类型。模板详见第 12 章和 26 章。</p>
<p>与 std::array 相同，vector 类模板支持 CTAD，允许按下列方式定义 myVector。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>vector myVector<span class="token punctuation">{</span><span class="token number">11</span><span class="token punctuation">,</span> <span class="token number">22</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>再次说明，需要初始化器才能使 CTAD 正常工作。以下是非法的：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>vector myVector<span class="token punctuation">;</span>
</code></pre><p>为向 vector 中添加元素，可使用 push_back() 方法。可使用类似数组的语法（即 operator[]）访问各个元素。</p>
<h4 id="1117-stdpair">1.1.17 std::pair </h4>
<p>std::pair 类模板定义在 &lt;utility&gt; 中。它可将两个不同类型的值组合在一起。可通过 first 和 second 公共数据成员访问这些值。这是一个例子：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>pair<span class="token operator">&lt;</span><span class="token keyword keyword-double">double</span><span class="token punctuation">,</span> <span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span> myPair<span class="token punctuation">{</span><span class="token number">1.23</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
cout <span class="token operator">&lt;&lt;</span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"{} {}"</span><span class="token punctuation">,</span> myPair<span class="token punctuation">.</span>first<span class="token punctuation">,</span> myPair<span class="token punctuation">.</span>second<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>pair 也支持 CTAD，示例略。</p>
<h4 id="1118-stdoptional">1.1.18 std::optional </h4>
<p>在 &lt;optional&gt; 中定义的 std::optional 保留特定类型的值，或者不包含任何值。</p>
<p>基本上，如果想要允许值是可选的，则可以将 optional 用于函数的参数。如果函数可能返回可可能不返回某些内容，则通常也将 optional 用作函数的返回类型。这消除了从函数中返回“特殊”值的需要，例如 nullptr、end()、-1，EOF 之类的。它还消除了将函数编写为返回代表成功或失败的布尔值的需求，同时将函数的实际结果存储在作为输出参数传递给函数的实参中（类型为对非 const 的引用的参数）。</p>
<p>optional 类型是一个类模板，因此必须在简括号之间指定所需的实际类型，如 optional&lt;int&gt;。</p>
<p>这是一个返回 optional 的函数的例子：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>optional<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span> <span class="token function">getData</span><span class="token punctuation">(</span><span class="token keyword keyword-bool">bool</span> giveIt<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>giveIt<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-return">return</span> <span class="token number">42</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword keyword-return">return</span> nullopt<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>可以按下列方式调用这个函数：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>optional<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span> data1<span class="token punctuation">{</span><span class="token function">getData</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
optional<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span> data2<span class="token punctuation">{</span><span class="token function">getData</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>可以用 has_value() 方法判断一个 optional 是否有值，或简单地将 optional 用在 if 语句中。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>cout <span class="token operator">&lt;&lt;</span> <span class="token string">"data1.has_value = "</span> <span class="token operator">&lt;&lt;</span> data1<span class="token punctuation">.</span><span class="token function">has_value</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
<span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>data2<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    cout <span class="token operator">&lt;&lt;</span> <span class="token string">"data2 has a value."</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>如果 optional 有值，可以使用 value() 或解引用运算符访问它。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>cout <span class="token operator">&lt;&lt;</span> <span class="token string">"data1.value = "</span> <span class="token operator">&lt;&lt;</span> data1<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
cout <span class="token operator">&lt;&lt;</span> <span class="token string">"data2.value = "</span> <span class="token operator">&lt;&lt;</span> <span class="token operator">*</span>data1 <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
</code></pre><p>如果对一个空的 optional 使用 value()，将抛出 std::has_optional_access 异常。</p>
<p>value_or() 可以用来返回 optional 的值，如果 optional 为空，则返回指定的值。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>cout <span class="token operator">&lt;&lt;</span> <span class="token string">"data2.value = "</span> <span class="token operator">&lt;&lt;</span> data2<span class="token punctuation">.</span><span class="token function">value_or</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
</code></pre><p>注意，不能将引用保存在 optional 中，所以 optional&lt;T&amp;&gt; 是无效的。但是，可以将指针保存在 optional 中。</p>
<h4 id="1119-结构化绑定">1.1.19 结构化绑定 </h4>
<p>结构化绑定允许绑定多个变量，这些变量使用数组、结构图、pair 或元组中的元素以初始化。<br>
例如，假设有下面的数组：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>array values<span class="token punctuation">{</span><span class="token number">11</span><span class="token punctuation">,</span> <span class="token number">22</span><span class="token punctuation">,</span> <span class="token number">33</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>可声明 3 个变量 x、y 和 z，像下面这样使用数组中的 3 个值进行初始化。注意，必须为结构化绑定使用 auto 关键字。例如，不能用 int 替代auto。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-auto">auto</span> <span class="token punctuation">[</span>x<span class="token punctuation">,</span> y<span class="token punctuation">,</span> z<span class="token punctuation">]</span><span class="token punctuation">{</span>values<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>使用结构化绑定声明的变量必须与右侧表达式中的值数量匹配。</p>
<p>如果所有非静态成员都是公有的，也可将结构化绑定用于结构体。例如：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-struct">struct</span> <span class="token class-name">Point</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-double">double</span> m_x<span class="token punctuation">,</span> m_y<span class="token punctuation">,</span> m_z<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
Point point<span class="token punctuation">;</span>
point<span class="token punctuation">.</span>m_x <span class="token operator">=</span> <span class="token number">1.0</span><span class="token punctuation">;</span>
point<span class="token punctuation">.</span>m_y <span class="token operator">=</span> <span class="token number">2.0</span><span class="token punctuation">;</span>
point<span class="token punctuation">.</span>m_z <span class="token operator">=</span> <span class="token number">3.0</span><span class="token punctuation">;</span>
<span class="token keyword keyword-auto">auto</span> <span class="token punctuation">[</span>x<span class="token punctuation">,</span> y<span class="token punctuation">,</span> z<span class="token punctuation">]</span><span class="token punctuation">{</span>point<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>正如最后一个例子，以下代码将 pair 中的元素分解为单独的变量：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>pair myPair<span class="token punctuation">{</span><span class="token string">"hello"</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-auto">auto</span> <span class="token punctuation">[</span>theString<span class="token punctuation">,</span> theInt<span class="token punctuation">]</span><span class="token punctuation">{</span>myPair<span class="token punctuation">}</span><span class="token punctuation">;</span>
cout <span class="token operator">&lt;&lt;</span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"theString: {}"</span><span class="token punctuation">,</span> theString<span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
cout <span class="token operator">&lt;&lt;</span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"theInt: {}"</span><span class="token punctuation">,</span> theInt<span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
</code></pre><p>通过使用 auto&amp; 或 const auto&amp; 代替 auto，还可以使用结构化绑定语法创建一组对非 const 的引用或 const 引用。</p>
<h4 id="1120-循环">1.1.20 循环 </h4>
<p>C++ 提供了四种循环结构</p>
<ol>
<li>
<p>while 循环</p>
</li>
<li>
<p>do/while 循环</p>
</li>
<li>
<p>for 循环</p>
</li>
<li>
<p>基于区间的 for 循环<br>
这种循环允许方便的迭代容器中的元素，可用于C风格的数组、初始化列表，也可用于具有返回迭代器的 begin() 和 end() 函数的类型，例如 std::array、std::vector 等。演示如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code> <span class="token keyword keyword-void">void</span> <span class="token function">demo</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
 <span class="token punctuation">{</span>
     std<span class="token double-colon punctuation">::</span>array<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token operator">&gt;</span> arr <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
     <span class="token keyword keyword-for">for</span> <span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> i <span class="token operator">:</span> arr<span class="token punctuation">)</span> <span class="token punctuation">{</span>
         std<span class="token double-colon punctuation">::</span>cout <span class="token operator">&lt;&lt;</span> i <span class="token operator">&lt;&lt;</span> std<span class="token double-colon punctuation">::</span>endl<span class="token punctuation">;</span>
     <span class="token punctuation">}</span>
 <span class="token punctuation">}</span>
</code></pre></li>
</ol>
<h4 id="1121-初始化列表">1.1.21 初始化列表 </h4>
<p>初始化列表在 <em><strong>&lt;initializer_list&gt;</strong></em> 头文件中定义，利用初始化列表，可轻松编写能接受可变数量参数的函数。演示如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;initializer_list&gt;</span></span>

<span class="token keyword keyword-using">using</span> <span class="token keyword keyword-namespace">namespace</span> std<span class="token punctuation">;</span>

<span class="token keyword keyword-int">int</span> <span class="token function">makeSum</span><span class="token punctuation">(</span>initializer_list<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span> lst<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-int">int</span> total <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-for">for</span> <span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> value <span class="token operator">:</span> lst<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        total <span class="token operator">+=</span> value<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword keyword-return">return</span> total<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>makeSum() 函数接受一个整形类型的初始化列表作为参数，可按如下方式使用该函数：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-int">int</span> a <span class="token operator">=</span> <span class="token function">makeSum</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword keyword-int">int</span> b <span class="token operator">=</span> <span class="token function">makeSum</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">20</span><span class="token punctuation">,</span> <span class="token number">30</span><span class="token punctuation">,</span> <span class="token number">40</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">,</span> <span class="token number">60</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>此处函数必须都是整数，否则会报错。</p>
<h4 id="1125-统一初始化">1.1.25 统一初始化 </h4>
<p>在 C++11 之前，各类型的初始化并非总是统一的。例如：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-struct">struct</span> <span class="token class-name">CircleStruct</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-int">int</span>    x<span class="token punctuation">,</span> y<span class="token punctuation">;</span>
    <span class="token keyword keyword-double">double</span> radius<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-class">class</span> <span class="token class-name">CircleClass</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    <span class="token keyword keyword-int">int</span>    m_x<span class="token punctuation">,</span> m_y<span class="token punctuation">;</span>
    <span class="token keyword keyword-double">double</span> m_radius<span class="token punctuation">;</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">CircleClass</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> x<span class="token punctuation">,</span> <span class="token keyword keyword-int">int</span> y<span class="token punctuation">,</span> <span class="token keyword keyword-double">double</span> radius<span class="token punctuation">)</span>
        <span class="token operator">:</span> m_x<span class="token punctuation">{</span>x<span class="token punctuation">}</span>
        <span class="token punctuation">,</span> m_y<span class="token punctuation">{</span>y<span class="token punctuation">}</span>
        <span class="token punctuation">,</span> m_radius<span class="token punctuation">{</span>radius<span class="token punctuation">}</span>
    <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>在 C++11 之前，CircleStruct 类型变量和 CircleClass 类型变量的初始化是不同的。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>CircleStruct myCircle1 <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">2.5</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
CircleClass  myCircle2<span class="token punctuation">{</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">2.5</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>对于结构体版本，可使用 {…} 语法。然而，对于类版本，需要使用函数符号 (…) 调用构造函数。</p>
<p>自 C++11 以后，允许一律使用 {…} 语法初始化类型，如下所示。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>CircleStruct myCircle3 <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">2.5</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
CircleClass  myCircle4 <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">2.5</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>定义 myCircle4 时将自动调用 CircleClass 的构造函数。甚至等号也是可选的。因此下面的代码与前面的代码等价：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>CircleStruct myCircle5<span class="token punctuation">{</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">2.5</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
CircleClass  myCircle6<span class="token punctuation">{</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">2.5</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>使用统一初始化并不局限于结构和类，它还可用于初始化 C++ 中的任何内容。例如：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-int">int</span> a <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span>
<span class="token keyword keyword-int">int</span> b<span class="token punctuation">{</span><span class="token number">3</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-int">int</span> c <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token number">3</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-int">int</span> d<span class="token punctuation">{</span><span class="token number">3</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>统一初始化还可用于对变量进行零初始化，只需要指定一对空的大括号。例如：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-int">int</span> e<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>使用统一初始化的一个优点时可以阻止窄化（narrowing）。当使用旧式风格的赋值语法初始化变量时，C++ 隐式地执行窄化。例如：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-void">void</span> <span class="token function">func</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> i<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token comment">/* ... */</span>
<span class="token punctuation">}</span>

<span class="token keyword keyword-int">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-int">int</span> x <span class="token operator">=</span> <span class="token number">3.14</span><span class="token punctuation">;</span>
    <span class="token function">func</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>在 main() 的两行代码中，C++ 在对 x 赋值或调用 func() 之前，会自动将 3.14 截断为 3。在任何情况下，窄化转换都不应该被忽视，因为它们可能会引起细微的错误。使用统一初始化。如果编译器完全支持 C++11 标准，x 的赋值和 func() 的调用都会生成编译错误。</p>
<p>如果需要窄化转换，建议使用准则支持库（GSL）中提供的 gsl::narrow_cast() 函数。<br>
统一初始化还可用来初始化动态分配的数组：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-int">int</span><span class="token operator">*</span> myArray <span class="token operator">=</span> <span class="token keyword keyword-new">new</span> <span class="token keyword keyword-int">int</span><span class="token punctuation">[</span><span class="token number">4</span><span class="token punctuation">]</span><span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>从 C++20 开始，可以省略数组的大小 4。</p>
<p>统一初始化还可在构造函数初始化器中初始化类成员数组：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">myClass</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    <span class="token keyword keyword-int">int</span> m_array<span class="token punctuation">[</span><span class="token number">4</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">myClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token operator">:</span> m_array<span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">}</span>
    <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>统一初始化还可用于标准库容器，如 std::vector。</p>
<blockquote>
<p><strong>注意：</strong></p>
<p>考虑到所有这些好处，建议使用统一初始化，而不是使用赋值语法初始化变量。</p>
</blockquote>
<h5 id="指派初始化器">指派初始化器 </h5>
<p>C++20 引入了指派初始化器，以使用它们的名称初始化所谓聚合的数据成员。聚合类型时满足以下限制的数组类型的对象或结构或类的对象：仅 public 数据成员、无用户声明或继承的构造函数、无虚函数和无虚基类、private 或 protected 的基类（见第 10 章）。指派初始化器以点开头，后跟数据成员的名称。指派初始化的顺序的必须与数据成员的声明顺序相同。不允许混合使用指派初始化器和非指派初始化器。未使用指派初始化器初始化的任何数据成员都将使用其默认值进行初始化，这意味着：</p>
<ul>
<li>拥有类内初始化器的数据成员会得到该值。</li>
<li>没有类内初始化器的数据成员会被零初始化。</li>
</ul>
<p>如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-struct">struct</span> <span class="token class-name">Employee</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-char">char</span> firstInitial<span class="token punctuation">;</span>
    <span class="token keyword keyword-char">char</span> lastInitial<span class="token punctuation">;</span>
    <span class="token keyword keyword-int">int</span>  employeeNUmber<span class="token punctuation">;</span>
    <span class="token keyword keyword-int">int</span>  salary<span class="token punctuation">{</span><span class="token number">75000</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>在前面，这种 Employee 结构是使用如下的统一初始化语法初始化的：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Employee anEmployee<span class="token punctuation">{</span><span class="token char">'J'</span><span class="token punctuation">,</span> <span class="token char">'D'</span><span class="token punctuation">,</span> <span class="token number">42</span><span class="token punctuation">,</span> <span class="token number">80000</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>使用指派初始化器，可以被写成这样：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Employee anEmployee<span class="token punctuation">{</span><span class="token punctuation">.</span>firstInitial   <span class="token operator">=</span> <span class="token char">'J'</span><span class="token punctuation">,</span>   <span class="token comment">//</span>
                    <span class="token punctuation">.</span>lastInitial    <span class="token operator">=</span> <span class="token char">'D'</span><span class="token punctuation">,</span>
                    <span class="token punctuation">.</span>employeeNUmber <span class="token operator">=</span> <span class="token number">42</span><span class="token punctuation">,</span>
                    <span class="token punctuation">.</span>salary         <span class="token operator">=</span> <span class="token number">80000</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>使用指派初始化器的好处是，与使用统一初始化语法相比，它更容易理解指派初始化器正在初始化的内容。</p>
<p>使用指派初始化器，如果对某些成员的默认值感到满意，则可以跳过它们的初始化。</p>
<p>如果使用统一初始化语法，这是不可以的。</p>
<p>使用指派初始化器的最后一个好处是，当新成员被添加到数据结构时，使用指派初始化器的现有代码将继续器作用。新的数据成员将使用其默认值进行初始化。</p>
<h4 id="1126-指针和动态内存">1.1.26 指针和动态内存 </h4>
<h5 id="堆栈和堆">堆栈和堆 </h5>
<p>堆栈帧是为每个函数分配的独立的内存空间，在堆栈上分配内存的变量不需要由程序员释放内存（删除）。</p>
<p>堆使与当前函数或堆栈帧无关的内存区域。如果想在函数调用结束仍然保存其中声明的变量，可将变量放在堆中。必须确保释放（删除）在堆上分配的任何内存，这个过程不会自动完成，除非使用智能指针。</p>
<h5 id="使用指针">使用指针 </h5>
<h5 id="动态分配和数组">动态分配和数组 </h5>
<p>使用 <em><strong>new[]</strong></em> 操作符可给数组分配内存</p>
<p>使用完后，应使用 <em><strong>delete[]</strong></em> 操作符释放内存。</p>
<p>delete 后的方括号表明删除的是一个数组。</p>
<p>（应避免使用C中的 malloc() 和 free()）</p>
<h5 id="空指针常量">空指针常量 </h5>
<p><em><strong>nullptr</strong></em> 是 NULL 的 char* 版本。</p>
<p>用 nullptr 来替代 NULL</p>
<h5 id="智能指针">智能指针 </h5>
<p>（详见第 7 章）</p>
<p>C++有两种最重要的智能指针：<em><strong>std::unique_ptr</strong></em> 和 <em><strong>std::shared_ptr</strong></em>。</p>
<p>使用 <em><strong>std::make_unique&lt;&gt;()</strong></em> 和 <em><strong>std::make_shared&lt;&gt;()</strong></em> 创建智能指针。</p>
<p>unique_ptr 代表的是专属所有权，即由 unique_ptr 管理的内存，只能被一个对象持有。所以，unique_ptr <strong>不支持复制和赋值</strong>，如下</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-auto">auto</span> w  <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span><span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span>Widget<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword keyword-auto">auto</span> w2 <span class="token operator">=</span> w<span class="token punctuation">;</span>   <span class="token comment">// 编译错误</span>
</code></pre><p>因此，unique_ptr 只支持<strong>移动</strong>，如下</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-auto">auto</span> w  <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span><span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span>Widget<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword keyword-auto">auto</span> w2 <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span><span class="token function">move</span><span class="token punctuation">(</span>w<span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment">// w2 获得内存所有权，w 此时等于 nullptr</span>
</code></pre><p>shared_ptr 代表的是共享所有权，即多个 shared_ptr 可以共享一块内存，因此shared_ptr 是支持复制的。如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-auto">auto</span> w <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span><span class="token generic-function"><span class="token function">make_shared</span><span class="token generic class-name"><span class="token operator">&lt;</span>Widget<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-auto">auto</span> w2 <span class="token operator">=</span> w<span class="token punctuation">;</span>
    cout <span class="token operator">&lt;&lt;</span> w<span class="token punctuation">.</span><span class="token function">use_count</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>   <span class="token comment">// 2</span>
<span class="token punctuation">}</span>
cout <span class="token operator">&lt;&lt;</span> w<span class="token punctuation">.</span><span class="token function">use_count</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>   <span class="token comment">// 1</span>
</code></pre><p>同时，shared_ptr 也支持移动。从语义上看，移动是所有权的传递。如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-auto">auto</span> w  <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span><span class="token generic-function"><span class="token function">make_shared</span><span class="token generic class-name"><span class="token operator">&lt;</span>Widget<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword keyword-auto">auto</span> w2 <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span><span class="token function">move</span><span class="token punctuation">(</span>w<span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment">// 此时 w 等于 nullptr，w2.use_count() 等于 1</span>
</code></pre><h4 id="1127-const-的用法">1.1.27 const 的用法 </h4>
<p>基本上，关键字 const 是 constant 的缩写，它表示某些内容保持不变。编译器通过将任何试图将其更改的行为标记为错误，用来保证此需求。此外，启用优化后，编译器可以利用此知识生成更好的代码。</p>
<h5 id="const-修饰类型">const 修饰类型 </h5>
<ul>
<li>
<p>const 与指针<br>
存在一个简单的规则：const 关键字<strong>作用于其直接左侧的内容</strong>。</p>
<blockquote>
<p><strong>注意：</strong></p>
<p>有另一个易于记忆的规则，可以用于读懂复杂的变量声明：从右向左读。例如，int * const ip 从右到左读取，得到 “ip 是指向 int 的 const 的指针。”另外，int const * ip 读为 “ip 是指向 const int 的指针”、而 const int * ip 读为 “ip 是指向 int 常量的指针”。</p>
</blockquote>
</li>
<li>
<p>使用 const 保护参数</p>
</li>
</ul>
<h5 id="const-方法">const 方法 </h5>
<p>略。</p>
<blockquote>
<p><strong>注意：</strong></p>
<p>为了遵循 const-correctness 原则，建议将不改变对象的任何数据成员的成员函数声明为 const。与非 const 成员函数也被称为赋值函数（mutator）相对，这些成员函数书也称为检查器（inspector）。</p>
</blockquote>
<h4 id="1128-constexpr-关键字">1.1.28 constexpr 关键字 </h4>
<p>C++ 一直有常量表达式的概念，即在编译器求值的表达式，在某些情况下，必须使用常量表达式。例如，定义数组时，数组的大小需要为常量表达式。由于此限制，以下代码在 C++ 中无效。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-const">const</span> <span class="token keyword keyword-int">int</span> <span class="token function">getArraySize</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> <span class="token number">32</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword keyword-int">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-int">int</span> myArray<span class="token punctuation">[</span><span class="token function">getArraySize</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>使用 constexpr 关键字，getArraySize() 函数可以被重定义，允许在常量表达式用调用它。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-constexpr">constexpr</span> <span class="token keyword keyword-int">int</span> <span class="token function">getArraySize</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> <span class="token number">32</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword keyword-int">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-int">int</span> myArray<span class="token punctuation">[</span><span class="token function">getArraySize</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>将函数声明为 constexpr 对函数的功能施加了很多限制，因为编译器必须能够在编译时对函数求值。例如，允许 constexpr 函数调用其他 constexpr 函数，但不允许调用任何非 constexpr 函数。这样的函数不允许有任何副作用，也不能引发任何异常。</p>
<h4 id="1129-consteval-关键字">1.1.29 consteval 关键字 </h4>
<p>上一节的 constexpr 关键字指定函数可以在编译期执行，但不能保证一定在编译期执行，采用以下 constexpr 函数：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-constexpr">constexpr</span> <span class="token keyword keyword-double">double</span> <span class="token function">inchToMm</span><span class="token punctuation">(</span><span class="token keyword keyword-double">double</span> inch<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> inch <span class="token operator">*</span> <span class="token number">25.4</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>如果按以下方式调用，则会在编译时满足对函数求值。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-constexpr">constexpr</span> <span class="token keyword keyword-double">double</span> const_inch<span class="token punctuation">{</span><span class="token number">6.0</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-constexpr">constexpr</span> <span class="token keyword keyword-double">double</span> mml<span class="token punctuation">{</span><span class="token function">inchToMm</span><span class="token punctuation">(</span>const_inch<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>然而，如果按以下方式调用，函数将不会在编译期被求值，而是在运行时！</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-double">double</span> dynamic_inch<span class="token punctuation">{</span><span class="token number">8.0</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-double">double</span> mm2<span class="token punctuation">{</span><span class="token function">inchToMm</span><span class="token punctuation">(</span>dynamic_inch<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>如果确实希望保证始终在编译时对函数求值，则需要使用 C++20 的 consteval 关键字将函数转换为所谓的立即函数（immediate function）。可以按照如下方式更改 inchToMm() 函数：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-consteval">consteval</span> <span class="token keyword keyword-double">double</span> <span class="token function">inchToMm</span><span class="token punctuation">(</span><span class="token keyword keyword-double">double</span> inch<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> inch <span class="token operator">*</span> <span class="token number">25.4</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>现，对 inchToMm() 的第一次调用仍然可以正常编译，并且可以在编译期进行求值。但是，第二个调用现在会导致编译错误，因为无法在编译期对其进行求值。</p>
<h4 id="1130-引用">1.1.30 引用 </h4>
<p>C++ 中的引用（reference）时另一种变量的别名。对引用的所有修改都会更改其引用的变量的值。可以将引用视为隐式指针，它省去了获取变量地址和解引用指针的麻烦。另外，可以将引用视为原始变量的另一个名称。可以创建独立的引用变量，在类中使用引用数据成员，接受引用作为函数和方法的参数，并从函数和方法返回引用。</p>
<h5 id="1-引用变量">1. 引用变量 </h5>
<p>引用变量必须在创建时被初始化，例如：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-int">int</span>  x<span class="token punctuation">{</span><span class="token number">3</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-int">int</span><span class="token operator">&amp;</span> xRef<span class="token punctuation">{</span>x<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>给类型附加一个 &amp;，则指示相应的变量是一个引用。它仍然像正常变量一样被使用，但是在幕后，它实际上是指向原始变量的指针。</p>
<p>不允许在类定义外声明一个引用而不对其进行初始化。</p>
<blockquote>
<p><strong>警告：</strong></p>
<p>引用变量必须总是在创建时被初始化。</p>
</blockquote>
<h6 id="修改引用">修改引用 </h6>
<p>引用始终指向它初始化时的那个变量，引用一旦创建便无法更改。</p>
<p>示例略</p>
<blockquote>
<p><strong>警告：</strong></p>
<p>一旦将引用初始化为特定变量，就无法将引用更改为引用另一个变量。只能更改引用所指向的变量的值。</p>
</blockquote>
<h6 id="const-引用">const 引用 </h6>
<p>应用于引用的 const 通常比应用于指针的 const 容易。首先，引用默认是 const，因为不能更改它们的指向。因此，不需要显式标记它们为 const。其次，无法创建对引用的引用，因此通常只有一个间接引用级别。获得多个间接级别的唯一方法是创建对指针的引用。</p>
<p>因此，当 C++ 程序员提起 const 引用时，他们的意思是这样的：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-int">int</span>       z<span class="token punctuation">;</span>
<span class="token keyword keyword-const">const</span> <span class="token keyword keyword-int">int</span><span class="token operator">&amp;</span> zRef<span class="token punctuation">{</span>z<span class="token punctuation">}</span><span class="token punctuation">;</span>
zRef <span class="token operator">=</span> <span class="token number">4</span><span class="token punctuation">;</span>   <span class="token comment">// 无法编译</span>
</code></pre><p>通过将 const 应用于 int&amp;，可以阻止对 zRef 的赋值，如上所示。类似于指针，const int&amp; zRef 等价于 int const&amp; zRef，但是请注意，将 zRef 标记为 const 对 z 无效。仍然可以通过直接更改 z 的值而不是通过引用来更改 z 的值。</p>
<p>不能创建对未命名值的引用，例如整数字面量，除非该引用值是 const 值。在下面的示例中，unnameRef1 会编译失败，因为它是对非 const 的引用，却指向了一个常量。那以为着可以更改常数 5 的值，这没有任何意义。unnameRef2 之所以有效，是因为它是 const 引用，因此不能编写例如 unnameRef2 = 7 这样的代码。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-int">int</span><span class="token operator">&amp;</span>       unnameRef1<span class="token punctuation">{</span><span class="token number">5</span><span class="token punctuation">}</span><span class="token punctuation">;</span>   <span class="token comment">// 无法编译</span>
<span class="token keyword keyword-const">const</span> <span class="token keyword keyword-int">int</span><span class="token operator">&amp;</span> unnmaeRef2<span class="token punctuation">{</span><span class="token number">5</span><span class="token punctuation">}</span><span class="token punctuation">;</span>   <span class="token comment">// 编译通过</span>
</code></pre><p>临时对象也是如此。不能为临时对象创建一个非 const 的引用，但是 const 引用时可以的。例如，假设具有以下返回 std::string 对象的函数：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>string <span class="token function">getString</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> <span class="token string">"Hello World!"</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>可以为 getString() 的结果创建一个 const 引用，该引用将使临时 std::string 对象保持生命周期，直到该引用超出作用域。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>string<span class="token operator">&amp;</span>       string1<span class="token punctuation">{</span><span class="token function">getString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>   <span class="token comment">// 无法编译</span>
<span class="token keyword keyword-const">const</span> string<span class="token operator">&amp;</span> string2<span class="token punctuation">{</span><span class="token function">getString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>   <span class="token comment">// 编译通过</span>
</code></pre><h6 id="指针的引用和引用的指针">指针的引用和引用的指针 </h6>
<p>可以创建对任何类型的引用，包括指针类型。这是对指向 int 的指针的引用的示例：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-int">int</span><span class="token operator">*</span>  intP<span class="token punctuation">{</span><span class="token keyword keyword-nullptr">nullptr</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-int">int</span><span class="token operator">*</span><span class="token operator">&amp;</span> ptrRef<span class="token punctuation">{</span>intP<span class="token punctuation">}</span><span class="token punctuation">;</span>
ptrRef  <span class="token operator">=</span> <span class="token keyword keyword-new">new</span> <span class="token keyword keyword-int">int</span><span class="token punctuation">;</span>
<span class="token operator">*</span>ptrRef <span class="token operator">=</span> <span class="token number">5</span><span class="token punctuation">;</span>
</code></pre><p>语义很简单：ptrRef 是对 intP 的引用，intP 是对 int 的指针。修改 ptrRef 会更改 intP。</p>
<p>取一个引用的地址与取该引用所指向的变量的地址得到的结果使相同的。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-int">int</span>  x<span class="token punctuation">{</span><span class="token number">3</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-int">int</span><span class="token operator">&amp;</span> xRef<span class="token punctuation">{</span>x<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-int">int</span><span class="token operator">*</span> xPtr<span class="token punctuation">{</span><span class="token operator">&amp;</span>xRef<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token operator">*</span>xPtr <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">;</span>
</code></pre><p>最后要注意，不能声明对引用的引用或对引用的指针。例如，int&amp;&amp; 和 int&amp;* 都是不允许的。</p>
<h6 id="结构化绑定和引用">结构化绑定和引用 </h6>
<p>前面介绍过结构化绑定。给出了如下的例子：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>pair myPair<span class="token punctuation">{</span><span class="token string">"hello"</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-auto">auto</span> <span class="token punctuation">[</span>theString<span class="token punctuation">,</span> theInt<span class="token punctuation">]</span><span class="token punctuation">{</span>myPair<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>引用和 const 变量也可以与结构化绑定一起使用。示例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-auto">auto</span><span class="token operator">&amp;</span> <span class="token punctuation">[</span>theString<span class="token punctuation">,</span> theInt<span class="token punctuation">]</span><span class="token punctuation">{</span>myPair<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-const">const</span> <span class="token keyword keyword-auto">auto</span><span class="token operator">&amp;</span> <span class="token punctuation">[</span>theString<span class="token punctuation">,</span> theInt<span class="token punctuation">]</span><span class="token punctuation">{</span>myPair<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><h5 id="2-引用数据成员">2. 引用数据成员 </h5>
<p>类的数据成员可以是引用。如前所述，引用不能不指向其他变量而存在，并且不可以更改引用指向的变量。因此，引用数据成员不能在类构造函数的函数体内部进行初始化，必须在所谓的构造函数初始化器中进行初始化。</p>
<blockquote>
<p><strong>警告：</strong></p>
<p>引用必须始终在创建时被初始化。通常，引用是在声明时创建的，但是引用数据成员需要在类的构造函数初始化器中初始化。</p>
</blockquote>
<h5 id="3-引用作为函数参数">3. 引用作为函数参数 </h5>
<p>C++程序员通常不使用独立的引用变量或引用数据成员，引用的最常见用途是用于函数的参数。默认的参数传递语义是值传递（pass-by-value）：函数接收其参数的副本。修改这些参数后，原始实参保持不变。栈中变量的指针经常在 C 语言中使用，以允许函数修改其他栈帧中的变量。通过对指针解引用，函数可以修改表示该变量的内存，即使该变量不在当前的栈帧中。这种方法的问题在于，它将指针负载的语法带入了原本简单的任务。</p>
<p>相对于向函数传递指针，C++ 提供了一种更好的机制，称为引用传递（pass-by-reference），参数是引用而不是指针。以下是 addOne() 的两种实现，第一种对传入的变量没有影响，因为它是值传递的，因此该函数将接收传递给它的值的副本。第二种使用引用，因此更改了原始变量。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-void">void</span> <span class="token function">addOne</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> i<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    i<span class="token operator">++</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword keyword-void">void</span> <span class="token function">addOne</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span><span class="token operator">&amp;</span> i<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    i<span class="token operator">++</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>调用具有整形引用参数的 addOne() 的函数的语法与调用具有整型参数的 addOne() 函数没有区别。</p>
<p>这是另一个引用派上用场的例子，这是一个简单的交换函数，用于交换两个 int 类型的值。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-void">void</span> <span class="token function">swap</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span><span class="token operator">&amp;</span> first<span class="token punctuation">,</span> <span class="token keyword keyword-int">int</span><span class="token operator">&amp;</span> second<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-int">int</span> temp<span class="token punctuation">{</span>first<span class="token punctuation">}</span><span class="token punctuation">;</span>
    first  <span class="token operator">=</span> second<span class="token punctuation">;</span>
    second <span class="token operator">=</span> temp<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>当有一个指针但函数或方法只能接收引用时，就会产生一个常见的难题。在这种情况下，可以通过对指针解引用将其“转换”为引用。该操作提供了指针所指向的值，编译器随后使用该值初始化引用参数。例如，可以像这样调用 swap()：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-int">int</span>  x<span class="token punctuation">{</span><span class="token number">5</span><span class="token punctuation">}</span><span class="token punctuation">,</span> y<span class="token punctuation">{</span><span class="token number">6</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-int">int</span> <span class="token operator">*</span>xp<span class="token punctuation">{</span><span class="token operator">&amp;</span>x<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token operator">*</span>yp<span class="token punctuation">{</span><span class="token operator">&amp;</span>y<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token function">swap</span><span class="token punctuation">(</span><span class="token operator">*</span>xp<span class="token punctuation">,</span> <span class="token operator">*</span>yp<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>最后，如果函数需要返回一个复制成本高昂的类的对象，函数接收一个对该类的非 const 引用的输出参数，此后进行修改，而非直接返回对象。这是防止从函数返回对象时创建副本从而导致性能损失的推荐方法。</p>
<p>因此，我们有以下规则：</p>
<blockquote>
<p>警告：</p>
<p>从函数返回对象的推荐方法时通过值返回，而不是使用一个输出函数。</p>
</blockquote>
<h6 id="const-引用传递">const 引用传递 </h6>
<p>const 引用的参数的主要目的是效率，当将值传递给函数时，便会生成一个完整副本。传递引用时，实际只是传递指向原始对象的指针，因此计算机无需生成副本。通过 const 引用传递，可以做到二者兼顾：不生成副本，并且无法改变原始变量。当处理对象时，const 引用变得更重要，因为对象可能很大。并且对其进行复制可能会产生有害的副作用。示例略。</p>
<h6 id="值传递和引用传递">值传递和引用传递 </h6>
<p>当要修改参数并希望那些更改能够作用于传给函数的变量时，需要通过引用传递。但是，不应将引用传递的使用局限于那些情况。引用传递避免将实参复制到函数，从而提供了两个附加好处：</p>
<ul>
<li>效率：复制大型的对象可能花费很长时间，引用传递只是将该对象的一个引用传给了函数</li>
<li>支持：不是所有的类都允许值传递</li>
</ul>
<p>如果像利用这些好处，但又不想修改原始对象，则应该将参数标记为 const，从而可以传递 const 引用。</p>
<h5 id="4-引用作为返回值">4. 引用作为返回值 </h5>
<p>函数也可以返回引用。当然，只有在函数终止后返回的引用所指向的变量继续存在的情况下，才可以使用此方法。</p>
<blockquote>
<p><strong>警告：</strong></p>
<p>切勿返回作用域为函数内部的局部变量的引用，例如在函数结束时将被销毁的自动分配的栈上变量。</p>
</blockquote>
<h5 id="5-在引用与指针之间抉择">5. 在引用与指针之间抉择 </h5>
<p>引用使程序更简洁，更易于理解。他们也比指针安全，因为没有空引用，并且不需要显式解引用，因此不会遇到与指针相关的任何解引用错误。</p>
<p>大多数时候，可以使用引用而不是指针。与指向对象的指针相同，对对象的引用也支持所谓的多态性，详见第 10 章。</p>
<p>但是，某些情况下需要使用指针。一种情况是需要更改其指向的位置时。第二种情况是，指针是 optional 的，即当它可以是 nullptr 时，应避免使用原始指针，而使用所谓的智能指针（详见第 7 章），这是转换所有权的推荐方法。</p>
<p>考虑将一个整数数组分为两个数组的函数：分别存放奇数和偶数。该函数不知道源数组中的偶数或奇数个数，因此它应在检查源数组后为目标动态分配内存。它还应返回两个新数组的大小。总共有 4 项要返回：指向两个新数组的指针以及两个新数组的大小。显然，必须使用引用传递。规范的 C 的写法如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-void">void</span> <span class="token function">separateOddsAndEvens</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> <span class="token keyword keyword-int">int</span> arr<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> size_t size<span class="token punctuation">,</span> <span class="token keyword keyword-int">int</span><span class="token operator">*</span><span class="token operator">*</span> odds<span class="token punctuation">,</span>   <span class="token comment">//</span>
                          size_t<span class="token operator">*</span> numOdds<span class="token punctuation">,</span> <span class="token keyword keyword-int">int</span><span class="token operator">*</span><span class="token operator">*</span> evens<span class="token punctuation">,</span> size_t<span class="token operator">*</span> numEvens<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token operator">*</span>numOdds <span class="token operator">=</span> <span class="token operator">*</span>numEvens <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-for">for</span> <span class="token punctuation">(</span>size_t i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> size<span class="token punctuation">;</span> <span class="token operator">++</span>i<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">%</span> <span class="token number">2</span> <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token operator">++</span><span class="token punctuation">(</span><span class="token operator">*</span>numOdds<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token keyword keyword-else">else</span> <span class="token punctuation">{</span>
            <span class="token operator">++</span><span class="token punctuation">(</span><span class="token operator">*</span>numEvens<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>

    <span class="token operator">*</span>odds  <span class="token operator">=</span> <span class="token keyword keyword-new">new</span> <span class="token keyword keyword-int">int</span><span class="token punctuation">[</span><span class="token operator">*</span>numOdds<span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token operator">*</span>evens <span class="token operator">=</span> <span class="token keyword keyword-new">new</span> <span class="token keyword keyword-int">int</span><span class="token punctuation">[</span><span class="token operator">*</span>numEvens<span class="token punctuation">]</span><span class="token punctuation">;</span>

    size_t oddPos <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> evenPos <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-for">for</span> <span class="token punctuation">(</span>size_t i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> size<span class="token punctuation">;</span> <span class="token operator">++</span>i<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">%</span> <span class="token number">2</span> <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token punctuation">(</span><span class="token operator">*</span>odds<span class="token punctuation">)</span><span class="token punctuation">[</span>oddPos<span class="token operator">++</span><span class="token punctuation">]</span> <span class="token operator">=</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token keyword keyword-else">else</span> <span class="token punctuation">{</span>
            <span class="token punctuation">(</span><span class="token operator">*</span>evens<span class="token punctuation">)</span><span class="token punctuation">[</span>evenPos<span class="token operator">++</span><span class="token punctuation">]</span> <span class="token operator">=</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>该函数的最后 4 个参数是“引用”参数。若要更改它们引用的值，separateOddAndEvens() 必须对它们解引用，这回导致函数体内的语法丑陋。此外，当调用 separateOddAndEvens() 时，必须传递两个指针的地址，以便函数可以更改实际的指针，并传递两个 size_t 的地址，以便函数可以更改实际的 size_t。还要注意，调用方要负责删除由 separateOddAndEvens() 创建的两个数组。</p>
<p>使用示例略。</p>
<p>可以使用引用编写相同的函数，以获得真正的引用传递语义。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-void">void</span> <span class="token function">separateOddsAndEvens</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> <span class="token keyword keyword-int">int</span> arr<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> size_t size<span class="token punctuation">,</span> <span class="token keyword keyword-int">int</span><span class="token operator">*</span><span class="token operator">&amp;</span> odds<span class="token punctuation">,</span>   <span class="token comment">//</span>
                          size_t<span class="token operator">&amp;</span> numOdds<span class="token punctuation">,</span> <span class="token keyword keyword-int">int</span><span class="token operator">*</span><span class="token operator">&amp;</span> evens<span class="token punctuation">,</span> size_t<span class="token operator">&amp;</span> numEvens<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    numOdds <span class="token operator">=</span> numEvens <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-for">for</span> <span class="token punctuation">(</span>size_t i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> size<span class="token punctuation">;</span> <span class="token operator">++</span>i<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">%</span> <span class="token number">2</span> <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token operator">++</span>numOdds<span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token keyword keyword-else">else</span> <span class="token punctuation">{</span>
            <span class="token operator">++</span>numEvens<span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>

    odds  <span class="token operator">=</span> <span class="token keyword keyword-new">new</span> <span class="token keyword keyword-int">int</span><span class="token punctuation">[</span>numOdds<span class="token punctuation">]</span><span class="token punctuation">;</span>
    evens <span class="token operator">=</span> <span class="token keyword keyword-new">new</span> <span class="token keyword keyword-int">int</span><span class="token punctuation">[</span>numEvens<span class="token punctuation">]</span><span class="token punctuation">;</span>

    size_t oddPos <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> evenPos <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-for">for</span> <span class="token punctuation">(</span>size_t i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> size<span class="token punctuation">;</span> <span class="token operator">++</span>i<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">%</span> <span class="token number">2</span> <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            odds<span class="token punctuation">[</span>oddPos<span class="token operator">++</span><span class="token punctuation">]</span> <span class="token operator">=</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token keyword keyword-else">else</span> <span class="token punctuation">{</span>
            evens<span class="token punctuation">[</span>evenPos<span class="token operator">++</span><span class="token punctuation">]</span> <span class="token operator">=</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>在这种情况下，参数 odds 和 evens 是对 int* 的引用。separateOddAndEvens() 无需解引用就可以修改函数的实参 int*（引用传递）。相同的逻辑适用于 numOdds 和 numEvens，它们是对 size_ts 的引用，使用此版本的函数，不在需要传递指针或 size_ts 的地址。引用参数会自动处理。使用示例略。</p>
<p>即使使用引用参数已经不使用指针干净得多，但建议避免使用动态分配的数组。例如，使用标准库容器 vector。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-void">void</span> <span class="token function">separateOddAndEvens</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> vector<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span><span class="token operator">&amp;</span> arr<span class="token punctuation">,</span>   <span class="token comment">//</span>
                         vector<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span><span class="token operator">&amp;</span> odds<span class="token punctuation">,</span> vector<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span><span class="token operator">&amp;</span> evens<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-for">for</span> <span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> i <span class="token operator">:</span> arr<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>i <span class="token operator">%</span> <span class="token number">2</span> <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            odds<span class="token punctuation">.</span><span class="token function">push_back</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token keyword keyword-else">else</span> <span class="token punctuation">{</span>
            evens<span class="token punctuation">.</span><span class="token function">push_back</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>使用示例依然略。</p>
<p>使用向量的版本已经比使用指针或引用的版本好得多，但是正如之前所建议的那样，应尽可能避免使用输出参数。如果一个函数需要返回一些东西，它应该直接返回而不是使用输出参数。如果 object 是局部变量、函数参数或临时值，return object 格式的声明将会触发返回值优化（RVO）。此外，如果对象是局部变量，命名返回值优化（NRVO）将会生效。RVO 和 NRVO 都是复制省略（copy elision）的形式，使从函数中返回对象非常高效。使用复制省略功能，编译器可以避免复制从函数返回的对象，这构成零复制值传递语义。</p>
<p>以下版本的 separateOddsAndEvens() 返回一个简单的包含两个 vector 的结构体，而并不是接收两个输出向量作为参数。它也使用了 C++20 的指派初始化。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-struct">struct</span> <span class="token class-name">OddsAndEvens</span>
<span class="token punctuation">{</span>
    vector<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span> odds<span class="token punctuation">,</span> evens<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

OddsAndEvens <span class="token function">separateOddAndEvens</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> vector<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span><span class="token operator">&amp;</span> arr<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    vector<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span> odds<span class="token punctuation">,</span> evens<span class="token punctuation">;</span>
    <span class="token keyword keyword-for">for</span> <span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> i <span class="token operator">:</span> arr<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>i <span class="token operator">%</span> <span class="token number">2</span> <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            odds<span class="token punctuation">.</span><span class="token function">push_back</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token keyword keyword-else">else</span> <span class="token punctuation">{</span>
            evens<span class="token punctuation">.</span><span class="token function">push_back</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token keyword keyword-return">return</span> OddsAndEvens<span class="token punctuation">{</span><span class="token punctuation">.</span>odds <span class="token operator">=</span> odds<span class="token punctuation">,</span> <span class="token punctuation">.</span>evens <span class="token operator">=</span> evens<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><blockquote>
<p><strong>注意：</strong></p>
<p>不要使用输出参数，如果一个函数需要返回某些东西，直接按值返回即可。</p>
</blockquote>
<h4 id="1131-const_cast">1.1.31 const_cast() </h4>
<p>在 C++ 中，每个变量都有特定的类型。在某些情况下，有可能将一种类型的变量转换为另一种类型的变量。为此，C++ 提供了 5 种类型的转换：<em><strong>const_cast()</strong></em>、<em><strong>static_cast()</strong></em>、<em><strong>reinter_cast()</strong></em>、<em><strong>dynamic_cast()</strong></em> 和 <em><strong>std::bit_cast()</strong></em>（自 C++20 起）。其他类型的转换详见第 10 章。</p>
<p>const_cast() 可以使用它为变量添加或取消 const 属性。理论上，不需要 const 转换，如果变量是 const，应该保持 const。但在实际中，有时会遇到这样的情况：一个函数指定接收 const 参数，然后这个参数将在接收非 const 参数的函数中使用，并且可以确定后者不会修改其非 const 参数。“正确”的解决方案使使 const 在程序中一直保持，但这并不总是可行的，尤其是在使用第三方库的情况下。因此，有时需要舍弃变量的 const 属性，但是只有在确定所调用的函数不会修改该对象时，才应该这样做。否则，除了重构程序外，别无选择。这是一个例子：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-void">void</span> <span class="token function">ThirdPartyLibraryMethod</span><span class="token punctuation">(</span><span class="token keyword keyword-char">char</span><span class="token operator">*</span> str<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword keyword-void">void</span> <span class="token function">f</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> <span class="token keyword keyword-char">char</span><span class="token operator">*</span> str<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token function">ThirdPartyLibraryMethod</span><span class="token punctuation">(</span><span class="token generic-function"><span class="token function">const_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token keyword keyword-char">char</span><span class="token operator">*</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>str<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>此外，标准库提供了一个名为 <em><strong>std::as_const()</strong></em> 的辅助方法，该方法定义在 &lt;utility&gt; 中，该方法接收一个引用参数，返回它的 const 引用版本。基本上，as_const(obj) 等价于 const_cast&lt;const T&amp;&gt;(obj)，其中 T 是 obj 的类型。与使用 const_cast() 相比，使用 as_const() 可以使代码更短，更易读。基本用法如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>string <span class="token function">str</span><span class="token punctuation">(</span><span class="token string">"C++"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword keyword-const">const</span> string<span class="token operator">&amp;</span> constStr<span class="token punctuation">{</span><span class="token function">as_const</span><span class="token punctuation">(</span>str<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><h4 id="1132-异常">1.1.32 异常 </h4>
<p>C++ 是一种非常灵活的语言，但并不是非常安全。编译器允许编写改变随机内存地址或者尝试除以 0 的代码（计算机无法处理无穷大的数值）。异常（exceptions）就是试图增加一个安全等级的语言特性。</p>
<p>异常伴随着一些新术语。当某段代码检测到异常时，就会抛出（throw）一个异常，另一段代码会捕获（catch）这个异常并执行恰当的操作。下例给出一个名为 divideNumbers() 的函数，如果调用者传递给分母的值为 0，就会抛出一个异常。使用 std::invalid_argument 时需要 <em><strong>&lt;stdexcept&gt;</strong></em>。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-double">double</span> <span class="token function">divideNumbers</span><span class="token punctuation">(</span><span class="token keyword keyword-double">double</span> numerator<span class="token punctuation">,</span> <span class="token keyword keyword-double">double</span> denominator<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>denominator <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-throw">throw</span> invalid_argument<span class="token punctuation">{</span><span class="token string">"分母不能为 0。"</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword keyword-return">return</span> numerator <span class="token operator">/</span> denominator<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>当执行 throw 行时，函数将立刻结束而不会返回值。如果调用者将函数调用放到 try/catch 块中，就可以捕获异常并进行处理，如下面的代码所示。第 14 章详细介绍异常处理。注意，建议<strong>通过 const 引用捕获异常</strong>。还要注意，所有标准库异常类都有一个名为 <em><strong>what()</strong></em> 的方法，该方法返回一个字符串，其中包含对该异常的简要说明。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-try">try</span> <span class="token punctuation">{</span>
    cout <span class="token operator">&lt;&lt;</span> <span class="token function">divideNumbers</span><span class="token punctuation">(</span><span class="token number">2.5</span><span class="token punctuation">,</span> <span class="token number">0.5</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
    cout <span class="token operator">&lt;&lt;</span> <span class="token function">divideNumbers</span><span class="token punctuation">(</span><span class="token number">2.3</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
    cout <span class="token operator">&lt;&lt;</span> <span class="token function">divideNumbers</span><span class="token punctuation">(</span><span class="token number">4.5</span><span class="token punctuation">,</span> <span class="token number">2.5</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword keyword-catch">catch</span> <span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> invalid_argument<span class="token operator">&amp;</span> exception<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    cout <span class="token operator">&lt;&lt;</span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"Exception caught: {}"</span><span class="token punctuation">,</span> exception<span class="token punctuation">.</span><span class="token function">what</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>第一次调用正常返回，结果会输出给用户。第二次调用会抛出一个异常，不会返回值，第三次调用不会执行，因为第二次调用抛出了一个异常，导致程序跳转到 catch 块。</p>
<p>C++ 的异常非常灵活，为正确使用异常，需要理解抛出异常时栈变量的行为，必须正确捕获并处理必要的异常。另外，如果需要在异常中包含有关错误的更多信息，则可以编写自己的异常类型。最后，C++ 编译器不会强迫你捕获所有可能发生的异常。如果代码从不捕获异常但是引发了异常，则该程序将终止。详见第 14 章。</p>
<h4 id="1135-类型推断">1.1.35 类型推断 </h4>
<p>类型推断有两个关键字：<em><strong>auto</strong></em> 和 <em><strong>decltype</strong></em></p>
<h5 id="1-关键字-auto">1. 关键字 auto </h5>
<p>auto 有多种完全不同的含义：</p>
<ul>
<li>推断函数的返回类型</li>
<li>结构化绑定</li>
<li>推断表达式的类型</li>
<li>推断非类型模板参数的类型，见第 12 章</li>
<li>decltype(auto)，见第 12 章</li>
<li>其他语法</li>
<li>通用 lambda 表达式，见第 18 章</li>
</ul>
<p>始终要记住，auto <strong>去除</strong>了引用和 const 限定符，从而会建立<strong>副本</strong>，如果不需要副本，可使用 <em><strong>auto&amp;</strong></em> 或 <em><strong>const auto&amp;</strong></em></p>
<h5 id="2-关键字-decltype">2. 关键字 decltype </h5>
<p>关键字 decltype 把表达式作为实参，计算出该表达式的类型。例如：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-int">int</span>         x <span class="token operator">=</span> <span class="token number">123</span><span class="token punctuation">;</span>
<span class="token keyword keyword-decltype">decltype</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> y <span class="token operator">=</span> <span class="token number">456</span><span class="token punctuation">;</span>
</code></pre><p>auto 与 decltype 的区别在于，decltype 未去除引用和 const 限定符。</p>
<h2 id="2-使用-string-和-string_view">2 使用 string 和 string_view </h2>
<h3 id="21-动态字符串">2.1 动态字符串 </h3>
<h4 id="212-字符串字面量">2.1.2 字符串字面量 </h4>
<p>代码中如 “hello world” 等是一个字符串字面量（string literal），因为这个字符串以值的形式写出，而不是变量。</p>
<p>字符串字面量的类型为 “n 个 const char 的数组”，所以这些变量的类型应为 <em><strong>const char*</strong></em>。</p>
<ul>
<li>原始字符串字面量<br>
原始字符串字面量（raw string literal）是可横跨多行代码的字符串字面量，不需要嵌入的双引号。原始字符串字面量以 <em><strong>R"(</strong></em> 开头，以 <em><strong>)</strong></em> 结尾。</li>
<li>宽字符字面量（wchar_t 字符串）<br>
宽字符串支持 Unicode 字符来扩大表示范围。wchar_t 是字符类型，可容纳类 Unicode 字符，而且通常比 char（1 字节）更大。宽字符字面量要加上前缀 <em><strong>L</strong></em></li>
</ul>
<h4 id="213-c-stdstring-类">2.1.3 C++ std::string 类 </h4>
<p>string 类在 <em><strong>&lt;string&gt;</strong></em> 头文件中定义</p>
<p>详细可参阅标准库参考资源和附录 B。</p>
<p><em><strong>c_str()</strong></em> 返回 const char*，<em><strong>data()</strong></em> 返回 char*。</p>
<p><em><strong>to_string()</strong></em> 函数可将数值转换为字符串。</p>
<h4 id="214-stdstring_view-类">2.1.4 std::string_view 类 </h4>
<p>该类在 <em><strong>&lt;string_view&gt;</strong></em> 头文件中定义。string_view 基本上就是 const string&amp; 的简单替代品，但不会产生开销。缺少 c_str() 接口，但 data() 是可以用的。</p>
<p>无法从 string_view 隐式构建一个 string。要么使用一个显式的 string 构造函数，要么使用 string_view::data() 成员。</p>
<ul>
<li>
<p>std::string_view 字面量<br>
可使用标准的用户定义的字面量 <em><strong>sv</strong></em>，将字符串字面量解释为 std::string_view。例如：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-auto">auto</span> sv <span class="token operator">=</span> <span class="token string">"My string_view"</span>sv<span class="token punctuation">;</span>
</code></pre><p>标准的用户定义的字面量 sv 需要 using namespace std::string_view_literals;或 <em><strong>using namespace std</strong></em>。</p>
</li>
</ul>
<h2 id="3-编码风格">3 编码风格 </h2>
<h3 id="32-为代码编写文档">3.2 为代码编写文档 </h3>
<p>反正要多写注释就是了。</p>
<h3 id="33-分解">3.3 分解 </h3>
<p>分解(decomposition) 指将代码分为小段。理想状态下，每个函数或方法都应该只完成一个任务。任何非常复杂的子任务都应该分解为独立的函数或方法。</p>
<h4 id="331-通过重构分解">3.3.1 通过重构分解 </h4>
<p>重构(refactoring) 指重新构建代码的结构。详细参考附录B 列出的重构书籍。</p>
<ul>
<li>增强抽象的技术
<ul>
<li>封装字段：将字段设置为私有，使用获取器和设置器方法访问它们。</li>
<li>让类型通用：创建更通用的类型，以更好地共享代码。</li>
</ul>
</li>
<li>分割代码以使其更合理的技术
<ul>
<li>提取方法：将大方法的一部分转换为便于理解的新方法</li>
<li>提取类：将现有类的部分代码转移到新类中。</li>
</ul>
</li>
<li>增强代码名称和位置的技巧
<ul>
<li>移动方法或字段：移动到更合适的类或源文件中</li>
<li>重命名方法或字段：改为更能体现其作用的名称</li>
<li>上移(pull up)：在 OOP 中，移到基类中。</li>
<li>下移(push down)：在 OOp 中，移到派生类中。</li>
</ul>
</li>
</ul>
<p>重构代码时，必须依靠测试框架来捕获可能引入的缺陷。详见第 26 章。</p>
<h4 id="332-通过设计来分解">3.3.2 通过设计来分解 </h4>
<p>如果使用模块分解，可将以后编写的部分代码放在模块、方法或函数中。</p>
<p>仍然建议在编写代码之前设计程序。</p>
<h3 id="34-命名">3.4 命名 </h3>
<p>编译器有几个命名规则：</p>
<ul>
<li>名称不能以数字开头。</li>
<li>包含两个下划线的名称（例如 my__name）是保留名称，不应当使用。</li>
<li>以下划线开头的名称（例如 _Name）是保留名称，不应当使用。</li>
</ul>
<h4 id="341-选择恰当的名称">3.4.1 选择恰当的名称 </h4>
<p>一些适当和不当的名称</p>
<table>
<thead>
<tr>
<th>适当的名称</th>
<th>不当的名称</th>
</tr>
</thead>
<tbody>
<tr>
<td>sourceName、destination 区别两个对象</td>
<td>thing1、thing2 太笼统</td>
</tr>
<tr>
<td>gSettings 表明全局身份</td>
<td>globalUserSpecificSettingsAndPreferences 太长</td>
</tr>
<tr>
<td>mNameCounter 表明了数据成员身份</td>
<td>mNC 太简单，太模糊</td>
</tr>
<tr>
<td>calculateMarigoldOffset() 简单，明确</td>
<td>doAction() 太宽泛，不准确</td>
</tr>
<tr>
<td>mTypeString 赏心悦目</td>
<td>typeSTR256 只有计算机才会喜欢的名称</td>
</tr>
<tr>
<td>mWelshRarebit 好的内部玩笑</td>
<td>mIHateLarry 不恰当的内部玩笑</td>
</tr>
<tr>
<td>errorMEssage 描述性名称</td>
<td>String 非描述性名称</td>
</tr>
<tr>
<td>sourceFile、destinationFile 无缩写</td>
<td>srcFile、dstFile 缩写</td>
</tr>
</tbody>
</table>
<h4 id="342-命名约定">3.4.2 命名约定 </h4>
<ol>
<li>
<p>计数器<br>
编程时，经常把变量 “i” 和 “j” 用作计数器，使用二维数据时，将 row 和 column 用作索引会更容易。有些人也喜欢使用 outerLoopIndex 和 innerLoopIndex 等计数器。</p>
</li>
<li>
<p>前缀<br>
一些可用的前缀</p>
<table>
<thead>
<tr>
<th>前缀</th>
<th>实例名称</th>
<th>前缀的字面意思</th>
<th>用法</th>
</tr>
</thead>
<tbody>
<tr>
<td>m m_</td>
<td>mData m_data</td>
<td>成员”</td>
<td>类的数据成员</td>
</tr>
<tr>
<td>s ms ms_</td>
<td>sLookupTable msLookupTable ms_lookupTable</td>
<td>“静态”</td>
<td>静态变量或数据成员</td>
</tr>
<tr>
<td>k</td>
<td>kMaximumLength</td>
<td>“konstant”，德语表示的常量</td>
<td>常量值</td>
</tr>
<tr>
<td>b is</td>
<td>bCompleted isCompleted</td>
<td>“布尔值”</td>
<td>表示布尔值</td>
</tr>
<tr>
<td>n mNum</td>
<td>nLines mNumLines</td>
<td>“数字”</td>
<td>数据成员也是计数器</td>
</tr>
</tbody>
</table>
</li>
<li>
<p>匈牙利表示法</p>
</li>
<li>
<p>获取器和设置器<br>
如果类中包含数据成员，例如 mStatus，习惯上会通过获取器 getStatus() 和设置器 setStatus() 访问这个成员。要访问布尔数据成员，通常将 is 用作前缀，例如 isRunning()。</p>
</li>
<li>
<p>大写<br>
在 C++ 中，函数和方法通常将首字母大写（看个人喜好）。大写字母可用于为类和数据成员名指明单词的边界。</p>
</li>
<li>
<p>把常量放在名称空间中<br>
建议将常量放到不同的名称空间中。另一个更好的方法是使用枚举器。</p>
</li>
</ol>
<h2 id="6-设计可重用代码">6 设计可重用代码 </h2>
<h3 id="61-重用哲学">6.1 重用哲学 </h3>
<ul>
<li>编写一次，经常使用</li>
<li>尽量避免代码重复</li>
<li>DRY（Don’t Repeat Yourself，不要重写自己写过的代码）</li>
</ul>
<h3 id="62-如何设计可重用代码">6.2 如何设计可重用代码 </h3>
<p>可重用代码有两个主要目标。首先，代码必须<strong>通用</strong>，可用于稍有不同的目的或不同的应有程序域。涉及特定应用程序细节的组件很难被其他程序重用。</p>
<p>其次，可重用代码还应该<strong>易于使用</strong>，不需要花费大量时间去理解它们的接口或功能。</p>
<p>将<strong>库</strong>“递交”给客户的方法也很重要。可以源代码形式递交，这样客户只需要将源代码整合到他们的项目中。另一种选择时递交一个静态库，客户将该库连接到他们的应用程序；也可以给 Windows 客户递交一个动态链接库（<em><strong>DLL</strong></em>），给 Linux 客户递交一个共享对象（<em><strong>.so</strong></em>）。这些递交方式会对编写库的方式施加额外限制。</p>
<p>对于设计可重用代码而言，最重要的策略是<strong>抽象</strong>。当设计代码时，应该将<strong>接口</strong>和<strong>实现</strong>明确分离。这种分离使代码容易使用，主要是因为客户使用功能时，不需要理解内部实现细节。</p>
<p>抽象将代码分为接口和实现。首先，应该恰当的设计代码结构。使用什么样的类层次结构？应该使用模板吗？如何将代码分割为子系统？其次，必须设计接口，这是库或代码的“入口”，程序员用这个入口访问程序提供的功能。</p>
<h4 id="621-使用抽象">6.2.1 使用抽象 </h4>
<p>接口和实现的明确分离可杜绝用户以不希望的方式使用库，而这些方式可能导致意想不到的行为和bug。</p>
<p><strong>当设计界面时，不要向客户公开实现细节。</strong></p>
<p>有时为将某个接口返回的信息传递给其他接口，库要求客户代码保存这些信息。这一信息有时叫做<strong>句柄</strong>(<em><strong>handle</strong></em>)，经常用来跟踪某些特定的实例，这些实例调用时的状态需要被记住。如果库的设计需要句柄，不要公开句柄的内部情况。可将句柄放入某个<strong>不透明类</strong>，程序员不能“直接访问这个类的内部数据成员，也不能通过公有的获取器或设置器来访问”。<br>
不要要求客户代码改变句柄内部的变量。</p>
<p>抽象非常重要，应该贯穿于整个设计。在左除任何决定时，都应该确保满足抽象原则。将自己摆在客户的位置，判断是否需要接口的内部实现知识。这样将很少（甚至永远不）违背这个原则。</p>
<h4 id="622-构建理想的重用代码">6.2.2 构建理想的重用代码 </h4>
<p>在开始设计时就应该考虑重用，在所有的级别上都需要考虑，从单个函数、类乃至整个库或框架。在下文中，所有这些不同的级别称为<strong>组件</strong>。下面的策略有助于恰当的组织代码，注意所有这些策略都关注代码的<strong>通用性</strong>。设计可重用代码的另一方面是提供<strong>易用性</strong>。</p>
<h5 id="1-避免组合不相干的概念或者逻辑上独立的概念">1. 避免组合不相干的概念或者逻辑上独立的概念 </h5>
<p>当设计组件时，应该关注单个任务或一组任务，即“高聚合”，也称为 SRP(Single Responsibility Principle,单一责任原则)。不要把无关概念组合在一起。</p>
<p>即使设计的代码并不是专门用来重用的，也应该记住这一策略。整个程序本身很少会被重用，但是程序的片段或子系统可直接组合到其他用用程序中，也可以稍作变动后用于大致相同的环境。因此，设计程序时，应将逻辑独立的功能放到不同的组件中，以便在其他程序中使用。其中的每个组件都应当有明确定义。</p>
<ul>
<li>将程序分为逻辑子系统<br>
将子系统设计为可单独重用的分离组件，即“<strong>低耦合</strong>”。例如，如果设计一款网络游戏，应该将网络和图形用户界面放在独立的子系统中，这样就可以重用一个组件，而不会涉及另一个组件。</li>
<li>用类层次结构分离逻辑概念<br>
除了将程序分为逻辑子系统以外，在类级别上应该避免将无关概念组合在一起。（详细说明去看书）。<br>
与类级别一样，还应该避免在任何设计级别组合不相关的概念，也就是说，要追求“高聚合”。例如，在方法级别，一个方法不应该执行逻辑上无关的事项，不应当混合突变（设置）和审查（获取）等。</li>
<li>用聚合分离逻辑概念<br>
第 5 章讨论了聚合。为完成特定功能，对象会包含其他对象。当不适合使用继承方法时，可以使用聚合分离没有关系的功能或者有关系但独立的功能。<br>
（详细去看书）</li>
<li>消除用户界面的依赖性<br>
去看第 4 章。</li>
</ul>
<h5 id="2-对泛型数据结构和算法使用模板">2. 对泛型数据结构和算法使用模板 </h5>
<p>（去看书）</p>
<h5 id="3-提供恰当的检测和安全措施">3. 提供恰当的检测和安全措施 </h5>
<p>（书书书）</p>
<h5 id="4-扩展性">4. 扩展性 </h5>
<p>（书书书书）</p>
<h2 id="7-内存管理">7 内存管理 </h2>
<h3 id="71-使用动态内存">7.1 使用动态内存 </h3>
<h4 id="712-分配与释放">7.1.2 分配与释放 </h4>
<h5 id="1-使用-new-和-delete">1. 使用 new 和 delete </h5>
<p>要分配一块内存，可调用 new，并提供需要空间的变量的类型。new 返回指向那个内存的指针，如果忽略了 new 的返回值，或这个指针变量离开了作用域，那么这块内存就被孤立了，因为无法再访问这块内存。这也成为<strong>内存泄露</strong>。</p>
<p>建议在释放指针的内存后，将指针重新设置为 nullptr。这样就不会在无意中使用一个指向已释放内存的指针。</p>
<h5 id="2-关于-malloc-函数">2. 关于 malloc() 函数 </h5>
<p>new 相比 malloc() 的主要好处在于，new 不仅分配内存，还构建对象（调用相应的构造函数）。</p>
<p>类似的差异存在于 free() 函数和 delete 运算符之间。使用 free() 时，不会调用对象的析构函数。使用 delete 时，将调用析构函数来恰当的清理对象。</p>
<p>在 C++ 中不应该使用 malloc() 和 free() 函数。只使用 new 和 delete 运算符。</p>
<h5 id="3-当内存分配失败时">3. 当内存分配失败时 </h5>
<p>（去看第 14 章）</p>
<h4 id="713-数组">7.1.3 数组 </h4>
<p>数组将多个同一类型的变量封装在一个索引访问的变量中。</p>
<h5 id="1-基本类型的数组">1. 基本类型的数组 </h5>
<p>在堆栈上分配 5 个 int 型数字的局部数组的声明如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-int">int</span> myArray<span class="token punctuation">[</span><span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
</code></pre><p>在堆栈上声明数组时，数组的大小必须是编译时一直的常量值。</p>
<p>在堆上声明数组没有什么不同，只是需要通过一个指针引用数组的位置。代码如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-int">int</span><span class="token operator">*</span> myArrayPtr <span class="token operator">=</span> <span class="token keyword keyword-new">new</span> <span class="token keyword keyword-int">int</span><span class="token punctuation">[</span><span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
</code></pre><p>对 new[] 的每次调用都应与 delete[] 调用配对，以清理内存。例如：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-delete">delete</span><span class="token punctuation">[</span><span class="token punctuation">]</span> myArrayPtr<span class="token punctuation">;</span>
myArrayPtr <span class="token operator">=</span> <span class="token keyword keyword-nullptr">nullptr</span><span class="token punctuation">;</span>
</code></pre><p>把数组放在堆中的好处在于可在运行时通过动态内存指定数组的大小。但是不能把它和动态数组混为一谈。数组本身不是动态的，因为一旦被分配，数组的大小就不会改变。动态内存允许在运行时指定分配的内存块的大小，但它不会自动调整其大小以容纳数据。</p>
<p>不要在 C++ 中使用 <em><strong>realloc()</strong></em>。这个函数很危险。</p>
<h5 id="2-对象的数组">2. 对象的数组 </h5>
<p>略。</p>
<h5 id="3-删除数组">3. 删除数组 </h5>
<p>通过数组版本的 new(new[]) 分配内存时，必须通过数组版本的 delete(delete[]) 释放相应的内存。</p>
<h5 id="4-多维数组">4. 多维数组 </h5>
<ul>
<li>多维堆栈数组</li>
<li>多维堆数组</li>
</ul>
<p>略过了很多。</p>
<p>下面的函数正确的分配了二维数组的内存：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-char">char</span><span class="token operator">*</span><span class="token operator">*</span> <span class="token function">allocateCharacterBoard</span><span class="token punctuation">(</span>size_t xDimension<span class="token punctuation">,</span> size_t yDimension<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-char">char</span><span class="token operator">*</span><span class="token operator">*</span> myArray <span class="token operator">=</span> <span class="token keyword keyword-new">new</span> <span class="token keyword keyword-char">char</span><span class="token operator">*</span><span class="token punctuation">[</span>xDimension<span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-for">for</span> <span class="token punctuation">(</span>size_t i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> xDimension<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        myArray<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword keyword-new">new</span> <span class="token keyword keyword-char">char</span><span class="token punctuation">[</span>yDimension<span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword keyword-return">return</span> myArray<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>释放数组的代码如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-void">void</span> <span class="token function">releaseCharacterBoard</span><span class="token punctuation">(</span><span class="token keyword keyword-char">char</span><span class="token operator">*</span><span class="token operator">*</span> myArray<span class="token punctuation">,</span> size_t xDimension<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-for">for</span> <span class="token punctuation">(</span>size_t i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> xDimension<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-delete">delete</span><span class="token punctuation">[</span><span class="token punctuation">]</span> myArray<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword keyword-delete">delete</span><span class="token punctuation">[</span><span class="token punctuation">]</span> myArray<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>综上所述，不要使用旧式的 C 风格数组，应该用 C++标准库容器，例如 std::array、std::vector 等。</p>
<h4 id="731-指针运算">7.3.1 指针运算 </h4>
<p>如果声明一个指向 int 的指针，然后将这个指针递增 1，那么这个指针在内存中向前移动 1 个 int 的大小，而不是 1 个字节。此类操作对数组最有用。</p>
<p>指针运算的另一个有用应用是减法运算。将一个指针减去另一个同类型的指针，得到的是两个指针之间指针指向的类型的元素个数，而不是两个指针之间字节数的绝对值。</p>
<h3 id="74-智能指针">7.4 智能指针 </h3>
<p>智能指针可帮助管理动态分配的内存，这是避免内存泄漏建议采用的技术。</p>
<p>应将 unique_ptr 用作默认智能指针。仅当真正需要共享资源时，才使用 shared_ptr。<br>
永远不要将资源分配结果指定给普通指针。无论使用哪种资源分配方法，都应当立即将资源指针存储在智能指针 unique_ptr 或 shared_ptr 中，或其他 RAII 类（资源获取即初始化）。</p>
<h4 id="741-unique_ptr">7.4.1 unique_ptr </h4>
<h5 id="1-创建-unique_ptrs">1. 创建 unique_ptrs </h5>
<p>作为经验总结，总是将动态分配的对象保存在堆栈的 unique_ptr 实例中。</p>
<p>创建 unique_ptr 有两种方式，例如：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-auto">auto</span>               mySimplePtr <span class="token operator">=</span> <span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span>Simple<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
unique_ptr<span class="token operator">&lt;</span>Simple<span class="token operator">&gt;</span> <span class="token function">mySimpleSmartPtr</span><span class="token punctuation">(</span><span class="token keyword keyword-new">new</span> <span class="token function">Simple</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>建议使用 <em><strong>make_unique()</strong></em>，这样代码更便于读取。</p>
<h5 id="2-使用-unique_ptrs">2. 使用 unique_ptrs </h5>
<p>这个标准智能指针最大的一个亮点是：用户不需要学习大量的新语法，就可以获得巨大好处。像标准指针一样，仍可以使用 * 或 -&gt; 对智能指针进行解引用。</p>
<p><em><strong>get()</strong></em> 方法可用于直接访问底层指针。这可将指针传递给需要普通指针的函数。</p>
<p><em><strong>reset()</strong></em> 可释放 unique_ptr 的底层指针，并可根据需要将其改成另一个指针。</p>
<p><em><strong>release()</strong></em> 可断开 unique_ptr 与底层指针的连接。release() 方法返回资源的底层指针，然后将智能指针设置为 nullptr。</p>
<p>由于 unique_ptr 代表唯一拥有权，因此无法复制它。使用 <em><strong>std::move()</strong></em> 实用工具（见第 9 章），可使用移动语义将一个 unique_ptr 移到另一个。这用于显式移动所有权,如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Foo</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">Foo</span><span class="token punctuation">(</span>unique_ptr<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span> data<span class="token punctuation">)</span>
        <span class="token operator">:</span> <span class="token function">mData</span><span class="token punctuation">(</span><span class="token function">move</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span><span class="token punctuation">}</span>

<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    unique_ptr<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span> mData<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-void">void</span> <span class="token function">demo</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-auto">auto</span> myIntSmartPtr <span class="token operator">=</span> <span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token number">42</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    Foo  <span class="token function">f</span><span class="token punctuation">(</span><span class="token function">move</span><span class="token punctuation">(</span>myIntSmartPtr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h5 id="3-unique_ptr-和-c-风格数组">3. unique_ptr 和 C 风格数组 </h5>
<p>unique_ptr 适用于存储动态分配的旧式 C 风格数组。如下例：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-auto">auto</span> myvariableSizedArray <span class="token operator">=</span> <span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>即便如此，也建议使用标准容器库。</p>
<h5 id="4-自定义-deleter">4. 自定义 deleter </h5>
<p>如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-int">int</span><span class="token operator">*</span> <span class="token function">malloc_int</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> value<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-int">int</span><span class="token operator">*</span> p <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword keyword-int">int</span><span class="token operator">*</span><span class="token punctuation">)</span><span class="token function">malloc</span><span class="token punctuation">(</span><span class="token keyword keyword-sizeof">sizeof</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token operator">*</span>p     <span class="token operator">=</span> value<span class="token punctuation">;</span>
    <span class="token keyword keyword-return">return</span> p<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

unique_ptr<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token punctuation">,</span> <span class="token keyword keyword-decltype">decltype</span><span class="token punctuation">(</span>free<span class="token punctuation">)</span><span class="token operator">*</span><span class="token operator">&gt;</span> <span class="token function">myIntSmartPtr</span><span class="token punctuation">(</span><span class="token function">malloc_int</span><span class="token punctuation">(</span><span class="token number">42</span><span class="token punctuation">)</span><span class="token punctuation">,</span> free<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>这段代码使用 malloc_int() 给整数分配内存，unique_ptr 调用标准的 free() 函数来释放内存。慢慢理解吧。</p>
<h4 id="742-shared_ptr">7.4.2 shared_ptr </h4>
<p>shared_ptr 的用法与 unique_ptr 类似。</p>
<p><strong>总是</strong>使用 <em><strong>make_shared()</strong></em> 来创建 shared_ptr。</p>
<p>与 unique_ptr 一样， shared_ptr 也支持 get() 和 reset() 方法，唯一区别是，当调用 reset() 时，由于引用计数，仅在最后的 shared_ptr 销毁或重置时，才释放底层资源。注意，shared_ptr 不支持 release()。可使用 <em><strong>use_count()</strong></em> 来检索共享同一资源的 shared_ptr 实例数量。</p>
<p>shared_ptr 自定义 deleter 方法如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>shared_ptr<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span> <span class="token function">myIntSmartPtr</span><span class="token punctuation">(</span><span class="token function">malloc_int</span><span class="token punctuation">(</span><span class="token number">42</span><span class="token punctuation">)</span><span class="token punctuation">,</span> free<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>不必将自定义 deleter 的类型指定为模板类型参数，这比 unique_ptr 的自定义 deleter 更简便。</p>
<h5 id="1-强制转换-shared_ptr">1. 强制转换 shared_ptr </h5>
<p>可用于强制转换 shared_ptrs 的函数是 const_pointer_cast()、dynamic_pointer_cast() 和 static_pointer_cast()。C++17 后又添加了 reinterpret_pointer_cast()，详见第 11 章。</p>
<h5 id="2-引用计数的必要性">2. 引用计数的必要性 </h5>
<p>略。</p>
<p>即使有两个指向同一个对象的 shared_Ptr，对象也只销毁一次。</p>
<h5 id="3-别名">3. 别名 </h5>
<p>shared_ptr 支持所谓的别名。即允许一个 shared_ptr 与另一个 shared_ptr 共享一个指针（拥有的指针），但指向不同的对象（存储的指针）。例如，这可用于使用一个 shared_ptr 指向一个对象的成员，同时拥有该对象本身。例如：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Foo</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">Foo</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> value<span class="token punctuation">)</span>
        <span class="token operator">:</span> <span class="token function">mData</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>
    <span class="token punctuation">{</span><span class="token punctuation">}</span>
    <span class="token keyword keyword-int">int</span> mData<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-auto">auto</span> foo      <span class="token operator">=</span> <span class="token generic-function"><span class="token function">make_shared</span><span class="token generic class-name"><span class="token operator">&lt;</span>Foo<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token number">42</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword keyword-auto">auto</span> aliasing <span class="token operator">=</span> <span class="token generic-function"><span class="token function">shared_ptr</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>foo<span class="token punctuation">,</span> <span class="token operator">&amp;</span>foo<span class="token operator">-&gt;</span>mData<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>当两个 shared_ptr(foo 和 aliasing) 都销毁时，才销毁 Foo 对象。</p>
<p>“拥有的指针”用于引用计数；当对指针解引用或调用它的 get()时，将返回“存储的指针”。</p>
<h4 id="743-weak_ptr">7.4.3 weak_ptr </h4>
<p>weak_ptr 类与 shared_ptr 模板有关。weak_ptr 可包含由 shared_ptr 管理的资源的引用。 weak_ptr 不拥有这个资源，所以不能阻止 shared_ptr 释放资源。weak_ptr 销毁时（例如离开作用域）不会销毁它指向的资源；然而，它可用于判断资源是否已经被关联的shared_ptr  释放了。weak_ptr 的构造函数要求将另一个 shared_ptr 或另一个 weak_ptr 作为参数。为了访问 weak_ptr 中保存的指针，需要将 weak_ptr 转换为 shared_ptr。这有两个方法：</p>
<ul>
<li>使用 weak_ptr 实例的 <em><strong>lock()</strong></em> 方法。这个方法返回一个 shared_ptr。如果同时释放了与 weak_ptr 关联的 shared_ptr，返回的 shared_ptr 使 nullptr。</li>
<li>创建一个新的 shared_ptr 实例，将 weak_ptr 作为 shared_ptr 构造函数的参数。如果释放了与 weak_ptr 关联的 shared_ptr，将抛出 std::bad_weak_ptr 异常。</li>
</ul>
<h4 id="744-移动语义">7.4.4 移动语义 </h4>
<p>详见第 9 章。</p>
<h4 id="745-enable_shared_from_this">7.4.5 enable_shared_from_this </h4>
<p>std::enable_shared_from_this 混入类允许对象上的方法给自身安全的返回 shared_ptr 或 weak_ptr。（混入类详见第 28 章）</p>
<p>enable_shared_form_this 混入类给类添加了以下两个方法：</p>
<ul>
<li><em><strong>shared_from_this</strong></em>：返回一个 shared_ptr，它共享对象的所有权。</li>
<li><em><strong>weak_from_this</strong></em>：返回一个 weak_ptr，它跟踪对象的所有权。</li>
</ul>
<p>这是一项高级功能，以下是简单演示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Foo</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">enable_shared_from_this</span><span class="token operator">&lt;</span><span class="token class-name">Foo</span><span class="token operator">&gt;</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    shared_ptr<span class="token operator">&lt;</span>Foo<span class="token operator">&gt;</span> <span class="token function">getPointer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword keyword-return">return</span> <span class="token function">shared_from_this</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-int">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-auto">auto</span> ptr1 <span class="token operator">=</span> <span class="token generic-function"><span class="token function">make_shared</span><span class="token generic class-name"><span class="token operator">&lt;</span>Foo<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-auto">auto</span> ptr2 <span class="token operator">=</span> ptr1<span class="token operator">-&gt;</span><span class="token function">getPointer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>注意，仅当对象的指针已经存储在 shared_ptr 时，才能使用对象上的 shared_from_this()。下面的实现是完全错误的：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Foo</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">enable_shared_from_this</span><span class="token operator">&lt;</span><span class="token class-name">Foo</span><span class="token operator">&gt;</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    shared_ptr<span class="token operator">&lt;</span>Foo<span class="token operator">&gt;</span> <span class="token function">getPointer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword keyword-return">return</span> <span class="token generic-function"><span class="token function">shared_ptr</span><span class="token generic class-name"><span class="token operator">&lt;</span>Foo<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token keyword keyword-this">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>如果像前面那样为 main() 使用相同的代码，该实现将会导致双重删除。</p>
<h3 id="75-常见的内存陷阱">7.5 常见的内存陷阱 </h3>
<p>每个内存泄漏或错误指针都有微妙的差别，没有解决所有内存问题的灵丹妙药，但有一些常见类型的问题是可以检测和解决的。</p>
<h4 id="751-分配不足的字符串">7.5.1 分配不足的字符串 </h4>
<p>与 C 风格字符串相关的最常见问题是分配不足。</p>
<p>有三种方法用于解决可能的分配不足问题。按照优先级降序排列，这三种方法为：</p>
<ol>
<li>使用 C++ 风格的字符串，它可自动处理与连接字符串关联的内存。</li>
<li>不要将缓冲区分配为全局变量或分配在堆栈上，而是分配在堆上。当剩余内存空间不足时，分配一个新缓冲区，它大到至少能保存当前内容加上新内存块的内容，将原来缓冲区的内容复制到新缓冲区，将新内容追加到后面，然后删除原来的缓冲区。</li>
</ol>
<h4 id="752-访问内存越界">7.5.2 访问内存越界 </h4>
<p>指针只不过是一个内存地址，因此指针可能指向内存中的任何一个位置。例如，考虑一个 C 风格的字符串，它不小心丢失了 ‘\0’ 终止字符。一个函数视图将字符串填满字符，但实际上可能会继续在字符串后面填充。</p>
<p>写入数组尾部后面的内存产生的 bug 称为缓冲区溢出错误。使用像 C++ string 和 vector 这样的高级结构有助于避免产生一些和 C 风格字符串和数组相关的 bug。</p>
<h4 id="753-内存泄漏">7.5.3 内存泄漏 </h4>
<p>通过智能指针避免内存泄露是解决这个问题的首选方法。</p>
<ol>
<li>通过 Visual C++ 在 Windows 中查找和修复内存泄漏</li>
<li>在 Linux 中通过 <em><strong>Valgrind</strong></em> 查找并修复修复内存泄漏<br>
太复杂啦暂略。</li>
</ol>
<h4 id="754-双重删除和无效指针">7.5.4 双重删除和无效指针 </h4>
<p>通过 delete 释放某个指针关联的内存时，这个内存就可以由程序的其他部分使用了。然而，无法禁止再次使用这个指针，这个指针成为悬挂指针。双重删除也是一个问题。如果第二次在同一个指针上执行 delete 操作，程序可能会释放重新分配给另一个对象的内存。</p>
<p>如果不按推荐的方式使用智能指针而是使用普通指针，至少在释放指针关联的内存后，将指针设置为 nullptr。这样能防止不小心两次删除同一个指针个使用无效的指针。注意，在 nullptr 指针上调用 delete 是<strong>允许</strong>的，只是这样没有任何效果。</p>
<h2 id="8-熟悉类和对象">8 熟悉类和对象 </h2>
<h3 id="82-编写类">8.2 编写类 </h3>
<h4 id="821-类定义">8.2.1 类定义 </h4>
<h5 id="1-类的成员">1. 类的成员 </h5>
<p>类可有许多成员。成员可以是成员函数（方法、构造函数或析构函数），也可以是成员变量（也成为数据成员）、成员枚举、类型别名和嵌套类等。</p>
<h5 id="2-访问控制">2. 访问控制 </h5>
<p>类中的每个方法和成员都可用三种访问说明符之一来说明：<em><strong>public</strong></em>、<em><strong>protected</strong></em> 或 <em><strong>private</strong></em>。访问说明符将应用于其后声明的所有成员，直到遇到另一个访问说明符。</p>
<p>类的默认访问说明符是 private：在第一个访问说明符之前声明的所有成员的访问都是私有的。</p>
<p>与类类似，C++ 中的结构体（struct）也可以拥有方法。实际上，唯一的区别就是结构的默认访问说明符是 public。</p>
<p>如果只需要一组可供公共访问的数据成员，没有方法或方法数量很少，习惯上用 struct 替代 class。</p>
<p>以下总结了这三种访问说明符的含义</p>
<ul>
<li>public：任何代码都可调用对象的 public 成员函数或者访问 public 数据成员<br>
想让客户使用的行为（方法），访问 private 和 protected 数据成员的方法</li>
<li>protected：类的任意成员函数都可调用 protected 成员函数或者访问 protected 数据成员。派生类的成员函数可访问基类的 protected 成员<br>
不想让客户使用的“帮助”方法</li>
<li>private：只有这个类的成员函数可调用 private 成员函数并访问 private 数据成员。派生类的成员函数不能访问基类的 private 成员。<br>
<strong>所有对象都应默认为 private</strong>，尤其是数据成员。如果只允许派生类访问它们，就可以提供受保护的获取器和设置器，如果希望客户能访问它们，就可以提供公有的获取器和设置器。</li>
</ul>
<h5 id="3-声明顺序">3. 声明顺序 </h5>
<p>可使用任何顺序声明成员和访问控制符。当然，为清晰起见，最好将 public、protected 和 private 声明分组，并在这些声明内将成员函数和数据成员分组。</p>
<h5 id="4-类内成员初始化器">4. 类内成员初始化器 </h5>
<p>可直接在类定义中初始化成员变量。</p>
<h4 id="822-定义方法">8.2.2 定义方法 </h4>
<p>与编写独立函数的原型和定义类似，必须编写方法的原型和定义。注意，类定义必须在方法定义之前。通常类定义在头文件中，方法定义在包含头文件的源文件中。</p>
<p>定义方法时，在方法名前都出现了类名和两个冒号：</p>
<p><em><strong>::</strong></em> 称为域解析运算符。再此环境中，这个语法告诉编译器，要定义的方法是哪个类的一部分。此外还要注意，定义方法时，不要重复使用访问说明符。</p>
<ol>
<li>访问数据成员<br>
略，就是用 set 和 get 方法。</li>
<li>调用其他方法<br>
略，就是用函数重载</li>
<li>this 指针<br>
略，懂得都懂。</li>
</ol>
<h4 id="823-使用对象">8.2.3 使用对象 </h4>
<ol>
<li>堆栈中的对象<br>
略。</li>
<li>堆中的对象<br>
略。</li>
</ol>
<h3 id="83-对象的生命周期">8.3 对象的生命周期 </h3>
<h4 id="831-创建对象">8.3.1 创建对象 </h4>
<h5 id="1-编写构造函数">1. 编写构造函数 </h5>
<p>构造函数<strong>没有返回类型</strong>，可以有也可以没有参数，没有参数的构造函数称为默认构造函数。许多情况下，必须提供默认构造函数。</p>
<h5 id="2-使用构造函数">2. 使用构造函数 </h5>
<p>构造函数用来创建对象并初始化其值。在基于堆栈和堆分配时可以使用构造函数。</p>
<p>略啦。</p>
<h5 id="3-提供多个构造函数">3. 提供多个构造函数 </h5>
<p>再略。</p>
<h5 id="4-默认构造函数">4. 默认构造函数 </h5>
<p>如果没有指定任何构造函数，编译器将自动生成无参构造函数。然而，如果声明了默认构造函数或其他构造函数，编译器就不会再自动生成默认构造函数。</p>
<ul>
<li>
<p>显式的默认构造函数<br>
在 C++03 或更早版本中，如果类需要一些接受参数的显式构造函数，还需要一个什么都不做的默认构造函数。<br>
为了避免手动编写空的默认构造函数，可按如下方法编写类的定义：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">SpreadsheetCell</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">SpreadsheetCell</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token function">SpreadsheetCell</span><span class="token punctuation">(</span><span class="token keyword keyword-double">double</span> initialValue<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">SpreadsheetCell</span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span>string_view initialValue<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre></li>
<li>
<p>显式删除构造函数<br>
如果一个类只有静态方法，没有任何构造函数，也不想让编译器生成默认构造函数。在此情况下可以显式删除默认构造函数：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">MyClass</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">MyClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-delete">delete</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre></li>
</ul>
<h5 id="5-构造函数初始化器">5. 构造函数初始化器 </h5>
<p>C++ 提供了另一种在构造函数中初始化数据成员的方法，叫做构造函数初始化器或 ctor-initializer。如下例：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token class-name">SpreadsheetCell</span><span class="token double-colon punctuation">::</span><span class="token function">SpreadsheetCell</span><span class="token punctuation">(</span><span class="token keyword keyword-double">double</span> initialValue<span class="token punctuation">)</span>
    <span class="token operator">:</span> <span class="token function">mValue</span><span class="token punctuation">(</span>initialValue<span class="token punctuation">)</span>
<span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre><p>这个列表出现在构造函数参数列表和构造函数体的左大括号之间，以冒号开始，由逗号间隔。列表中的每个元素都使用函数符号、统一的初始化语法、调用基类构造函数，或者委托构造函数以初始化某个数据成员。</p>
<p>这个方法是在创建数据成员时，为其调用构造函数。这样做效率比在后面赋值高。</p>
<p>如果类的数据成员是具有默认构造函数的类的对象，则不必在 ctor-initializer 中显式初始化对象。</p>
<p>而如果类的数据成员是没有默认构造函数的类的对象，则必须在 ctor-initializer 中显式初始化对象。</p>
<p>某些数据成员必须在 ctor-initializer 中或使用类内初始化器进行初始化。</p>
<table>
<thead>
<tr>
<th>数据类型</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>const 数据成员</td>
<td>const 变量创建后无法对其正确赋值，必须在创建时提供值</td>
</tr>
<tr>
<td>没有默认构造函数的对象数据成员</td>
<td>C++ 尝试用默认构造函数初始化成员对象。如果不存在默认构造函数，就无法初始化它们</td>
</tr>
<tr>
<td>没有默认构造函数的基类</td>
<td>见第 10 章</td>
</tr>
</tbody>
</table>
<p>使用 ctor-initializer  初始化数据成员的顺序如下：<strong>按照类定义中声明的顺序</strong>。</p>
<h5 id="6-复制构造函数">6. 复制构造函数 </h5>
<p>复制构造函数，允许所创建的对象是另一个对象的精确副本。如果没有编写复制构造函数，C++ 会自动生成一个，用源对象中相应数据成员的值初始化新对象的每个数据成员。如果数据成员是对象，初始化意味着调用它们的复制构造函数。</p>
<p>复制构造函数采用源对象的 <strong>const 引用</strong>作为参数</p>
<p>多数情况下，不需要亲自编写复制构造函数。</p>
<p>可将复制构造函数定义为显式默认或显式删除</p>
<p>通过删除复制构造函数，将不再复制对象。这可用于禁止按值传递对象。</p>
<h5 id="7-初始化列表构造函数">7. 初始化列表构造函数 </h5>
<p>初始化列表构造函数（initializer-list constructors）将 std::initializer_list&lt;T&gt; 作为第一个参数，并且没有任何其他参数（或者其他参数具有默认值）。在使用 std::initializer+list&lt;T&gt; 模板之前，必须包含 &lt;initializer&gt; 头文件。</p>
<p>示例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;initializer_list&gt;</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;stdexcept&gt;</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;vector&gt;</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;iostream&gt;</span></span>

<span class="token keyword keyword-using">using</span> <span class="token keyword keyword-namespace">namespace</span> std<span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">EvenSequence</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">EvenSequence</span><span class="token punctuation">(</span>initializer_list<span class="token operator">&lt;</span><span class="token keyword keyword-double">double</span><span class="token operator">&gt;</span> args<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>args<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">%</span> <span class="token number">2</span> <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword keyword-throw">throw</span> <span class="token function">invalid_argument</span><span class="token punctuation">(</span><span class="token string">"initializer_list should"</span>
                                   <span class="token string">"contain even number of elements."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        mSequence<span class="token punctuation">.</span><span class="token function">reserve</span><span class="token punctuation">(</span>args<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword keyword-for">for</span> <span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> <span class="token keyword keyword-auto">auto</span><span class="token operator">&amp;</span> value <span class="token operator">:</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            mSequence<span class="token punctuation">.</span><span class="token function">push_back</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>

    <span class="token keyword keyword-void">void</span> <span class="token function">dump</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span>
    <span class="token punctuation">{</span>
        <span class="token keyword keyword-for">for</span> <span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> <span class="token keyword keyword-auto">auto</span><span class="token operator">&amp;</span> value <span class="token operator">:</span> mSequence<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            cout <span class="token operator">&lt;&lt;</span> value <span class="token operator">&lt;&lt;</span> <span class="token string">", "</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        cout <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    vector<span class="token operator">&lt;</span><span class="token keyword keyword-double">double</span><span class="token operator">&gt;</span> mSequence<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><h5 id="8-委托构造函数">8. 委托构造函数 </h5>
<p>委托构造函数（delegating constructors）允许构造函数调用同一个类的其他构造函数。然而，这个调用不能放在构造函数体内，而必须放在构造函数初始化器中，且必须是列表中唯一的成员初始化器。</p>
<p>示例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token class-name">SpreadsheetCell</span><span class="token double-colon punctuation">::</span><span class="token function">SpreadsheetCell</span><span class="token punctuation">(</span>string_view initialValue<span class="token punctuation">)</span>
    <span class="token operator">:</span> <span class="token function">SpreadsheetCell</span><span class="token punctuation">(</span><span class="token function">stringToDouble</span><span class="token punctuation">(</span>initialValue<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre><p>当使用委托构造函数时，要注意避免出现构造函数的递归。</p>
<h5 id="9-总结编译器生成的构造函数">9. 总结编译器生成的构造函数 </h5>
<p>略。</p>
<p>注意默认构造函数和复制构造函数之间缺乏对称性。只要没有显式定义复制构造函数，编译器就会自动生成一个。另一方面，只要定义了任何构造函数，编译器就不会生成默认构造函数。</p>
<p>可通过将构造函数定义为显式默认或显式删除来影响自动生成的默认构造函数和默认复制函数。</p>
<p>构造函数的最后一种类型是<strong>移动构造函数</strong>，用于实现移动语义。详见第 9 章。</p>
<h4 id="832-销毁对象">8.3.2 销毁对象 </h4>
<p>当销毁对象时，会发生两件事：调用对象的析构函数，释放对象占用的内存。</p>
<h4 id="833-对象赋值">8.3.3 对象赋值 </h4>
<p>在 C++ 中，“复制”只在初始化对象时发生。如果一个已经具有值的对象被改写，更精确的术语是“赋值”。</p>
<p>因此，C++ 为所有的额类提供了执行赋值的方法，这个方法叫做<strong>赋值运算符（assignment operator）</strong>，名称是 operator=，因为实际上是为类重载了=运算符。</p>
<p>注意区分复制赋值运算符和<strong>移动赋值运算符</strong>。</p>
<h5 id="1-声明赋值运算符">1. 声明赋值运算符 </h5>
<p>示例：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">SpreadsheetCell</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    SpreadsheetCell<span class="token operator">&amp;</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>赋值运算符与复制构造函数类似，采用了源对象的 <strong>const 引用</strong>。在此情况下，将源对象称为 <em><strong>rhs</strong></em>，代表等号的“右边”（随便都好啦），调用赋值运算符的对象在等号的左边。</p>
<p>与复制构造函数不同的是，复制运算符返回对象的<strong>引用</strong>。原因是赋值可以链接在一起，如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>myCell <span class="token operator">=</span> anotherCell <span class="token operator">=</span> aThirdCell<span class="token punctuation">;</span>
</code></pre><p>实际上可让赋值运算符返回任意类型，包括 void，然而应该返回调用对象的引用，因为客户希望这样。</p>
<h5 id="2-定义赋值运算符">2. 定义赋值运算符 </h5>
<p>在 C++ 中允许将对象的值赋给自身。当唯一数据成员是基本类型时，这并不重要。但当类具有动态分配的内存或其他资源时，必须将自赋值考虑在内。为阻止此类问题发生，赋值运算符通常在方法开始时检测自赋值，如果发现自赋值，则立即返回。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>SpreadsheetCell<span class="token operator">&amp;</span> SpreadsheetCell<span class="token double-colon punctuation">::</span><span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span><span class="token keyword keyword-this">this</span> <span class="token operator">==</span> <span class="token operator">&amp;</span>rhs<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-return">return</span> <span class="token operator">*</span><span class="token keyword keyword-this">this</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>this 指针指向执行方法的对象，因此 *this 就是对象本身。编译器将返回一个对象的引用，从而与声明的返回值匹配。如果不是自赋值，就必须对每个成员赋值，最后返回 *this。</p>
<h5 id="3-显式地默认或删除赋值运算符">3. 显式地默认或删除赋值运算符 </h5>
<p>略。</p>
<h4 id="835-复制和赋值的区别">8.3.5 复制和赋值的区别 </h4>
<p>略。</p>
<h2 id="9-精通类与对象">9 精通类与对象 </h2>
<h3 id="91-友元">9.1 友元 </h3>
<p>C++ 允许某个类将其他类、其他类的成员函数或非成员函数声明为<strong>友元（friend）</strong>，友元可以访问类的 protected、private 数据成员和方法。</p>
<p>示例略。</p>
<p>注意类需要知道其他哪些类、方法或函数希望成为友元；类、方法或函数不能将自身声明为其他类的友元并访问这些类的非公有名称。</p>
<p>friend 类和方法很容易被滥用；友元可以违反封装的原则，将类的内部暴露给其他类或函数。因此，只有在特定的情况下才应该使用它们。</p>
<h3 id="92-对象中的动态内存分配">9.2 对象中的动态内存分配 </h3>
<h4 id="922-使用析构函数释放内存">9.2.2 使用析构函数释放内存 </h4>
<p>析构函数与类（和构造函数）同名，名称的前面有一个<strong>波浪号（~）</strong>。析构函数没有参数，并且只能有一个析构函数。析构函数永远不应抛出异常。</p>
<p>析构函数释放在构造函数中分配的内存。</p>
<h4 id="923-处理复制和赋值">9.2.3 处理复制和赋值 </h4>
<p>拷贝构造函数和赋值构造函数中必须进行深层复制。也就是说，它们不能只复制指针数据成员，必须复制指针所指向的实际数据。</p>
<p>无论什么时候，在类中动态分配内存后，都应该<strong>编写自己的拷贝构造函数和赋值运算符</strong>，以提供深层的内存复制。</p>
<p>需要一种全有或全无的机制：要么全部成功，要么该对象保持不变。为实现这个能安全处理异常的运算符，要使用 <mark>“复制和交换”惯用方法</mark>，可以给对象添加一个 <em><strong>swap()</strong></em> 方法，还需要一个非成员函数的 swap() 版本。在 &lt;utility&gt; 中的 std::swap() 提供。</p>
<p>示例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Spreadsheet<span class="token operator">&amp;</span> Spreadsheet<span class="token double-colon punctuation">::</span><span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Spreadsheet<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    Spreadsheet temp<span class="token punctuation">{</span>rhs<span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token function">swap</span><span class="token punctuation">(</span>temp<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-return">return</span> <span class="token operator">*</span><span class="token keyword keyword-this">this</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>“复制和交换”的惯用方法：</p>
<ul>
<li>第一阶段<strong>创建一个临时副本</strong>。这不修改对象的状态，因此，如果在这个阶段发生异常，不会出现问题。</li>
<li>第二阶段使用<em><strong>swap()</strong></em> 函数，将创建的临时副本与当前对象交换。swap() <strong>永远不会抛出异常</strong>。</li>
<li>第三阶段销毁临时对象以清理内存。</li>
</ul>
<p>如果不使用“复制和交换”惯用方法，那么就使用上面赋值的检查。</p>
<h4 id="924-使用移动语义处理移动">9.2.4 使用移动语义处理移动 </h4>
<p>对象的移动语义需要实现移动构造函数和移动复制运算符。移动将内存和其他资源的所有权从一个对象移动到另一个对象。</p>
<h5 id="1-右值引用">1. 右值引用 </h5>
<p>在 C++ 中，左值（lvalue）是可获取其地址的一个量，经常出现在赋值语句的左边。</p>
<p>右值引用是一个对右值（rvalue）的引用。这是一个当右值是临时对象或使用 <em><strong>std::move()</strong></em> 显式移动时才适用的概念。</p>
<p>函数可将 <em><strong>&amp;&amp;</strong></em> 作为参数说明的一部分（例如 type&amp;&amp; name），以指定右值引用参数。</p>
<p>可使用 std::move() 强迫编译器调用方法的右值引用版本。move() 做的唯一的事就是将左值转换为右值，也就是说，它不做任何实际的移动。</p>
<h5 id="2-实现移动语义">2. 实现移动语义 </h5>
<p>移动语义是通过右值引用实现的。为了对类增加移动语义，需要实现移动构造函数和移动赋值运算符，同时需要使用 <strong>noexcept 限定符</strong>标记。因为如果实现了移动语义，标准库容器会移动存储的对象，且确保不抛出异常。</p>
<p>示例略。</p>
<p>移动语义都将动态分配的内存所有权从源对象移动到新对象，将源对象的成员指针设置为空指针，以防源对象的析构函数释放这块内存。</p>
<p>在移动实现中可包含一个自我赋值检查。</p>
<p>定义在 &lt;utility&gt; 中的 <em><strong>std::exchange()</strong></em>，可以用一个新的值替代原来的值，并返回原来的值。</p>
<ul>
<li>
<p>使用 std::exchange<br>
在实现移动赋值运算符时，exchange() 十分有用。可以让代码更简介，如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-void">void</span> <span class="token class-name">Spreadsheet</span><span class="token double-colon punctuation">::</span><span class="token function">moveFrom</span><span class="token punctuation">(</span>Spreadsheet<span class="token operator">&amp;</span> src<span class="token punctuation">)</span> <span class="token keyword keyword-noexcept">noexcept</span>
<span class="token punctuation">{</span>
    m_name   <span class="token operator">=</span> <span class="token function">exchange</span><span class="token punctuation">(</span>src<span class="token punctuation">.</span>m_width<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    m_height <span class="token operator">=</span> <span class="token function">exchange</span><span class="token punctuation">(</span>src<span class="token punctuation">.</span>m_height<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    m_cells  <span class="token operator">=</span> <span class="token function">exchange</span><span class="token punctuation">(</span>src<span class="token punctuation">.</span>m_cells<span class="token punctuation">,</span> <span class="token keyword keyword-nullptr">nullptr</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></li>
<li>
<p>用交换方式实现移动构造函数和移动赋值运算符<br>
略。</p>
</li>
</ul>
<h5 id="3-测试移动操作">3. 测试移动操作 </h5>
<p>略。</p>
<h5 id="4-使用移动语义实现交换函数">4. 使用移动语义实现交换函数 </h5>
<p>略，但需要记住。</p>
<h5 id="5-在返回语句中使用-stdmove">5. 在返回语句中使用 std::move() </h5>
<p>当从函数中返回一个局部变量或参数时，只要写 return object; 就可以了，不要使用 std::move()。</p>
<h5 id="6-向函数传递参数的最佳方法">6. 向函数传递参数的最佳方法 </h5>
<p>为左值和右值优化方法总需要实现两个重载。而最好的办法是，<strong>始终使用 const 引用参数来传递对象</strong>，以避免任何不必要的复制。但是，书里还是建议使用<strong>值传递</strong>。随便啦。。。</p>
<p>对于函数本身将复制的参数，更倾向于值传递，但<strong>仅当该参数属于支持移动语义的类型</strong>时。否则，请使用 const引用参数。</p>
<h3 id="93-与方法有关的更多内容">9.3 与方法有关的更多内容 </h3>
<h4 id="931-static-方法">9.3.1 static 方法 </h4>
<p>与数据成员类似，方法有时会应用于全部类对象而不是单个对象。可以编写 static 和数据成员。一个类中如果方法没有访问特定对象的信息，可以将这个方法设置为 <em><strong>static</strong></em>，示例略。</p>
<p>注意 static 方法不属于特定对象，因此<strong>没有 this 指针</strong>。当用某个特定对象调用 static 方法时，static 方法不会访问这个对象的非 static 数据成员。实际上，static 方法就像普通函数，区别在于它可以访问类的 private 和 protected 的 static 成员。如果同一类型的其他对象对于 static 方法可见（例如<strong>传递了对象的指针或引用</strong>作为参数），那么 static 方法也可以访问其他对象的 private 和 protected 的非 static 数据成员。</p>
<h4 id="932-const方法">9.3.2 const方法 </h4>
<p>const 对象的值不能改变。如果使用 const 对象、const 对象的引用和指向 const 对象的指针，编译器将不允许调用对象的任何方法，除非这些方法保证不改变任何数据成员。为了保证方法不改变数据成员，可以用 <em><strong>const</strong></em> 关键字标记方法本身。</p>
<p>const 的工作原理时将方法内用到的数据成员都标记为 const 引用，因此如果试图修改数据成员，编译器会报错。</p>
<p><strong>不能将 static 方法声明为 const</strong>，因为这是多余的。静态方法没有类的实例，因此不可能改变内部的值。</p>
<p>非 const 对象可调用 const 方法和非 const 方法。然而，<strong>const 对象只能调用 const 方法</strong>。</p>
<p>应该养成习惯，<strong>将不修改对象的所有方法声明为 const</strong>，这样就可在程序中使用 const 对象的引用。注意 const 对象也会被销毁，它们的析构函数也会被调用，因此不应该将析构函数标记为 const。</p>
<h5 id="mutable-数据成员">mutable 数据成员 </h5>
<p>有时编写的方法“逻辑上”时 const 方法，但是碰巧改变了对象的数据成员。这个改动对于用户可见的数据没有任何影响，但严格来说确实做了改动，因此编译器不允许将这个方法声明为 const。</p>
<p>解决方法是将类中的这个会被修改的变量设置为 <em><strong>mutable</strong></em>，告诉编译器在 const()方法中允许改变这个值。示例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">SpreadsheetCell</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    <span class="token keyword keyword-double">double</span>         m_value<span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-mutable">mutable</span> size_t m_numAccess<span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><h4 id="933-方法重载">9.3.3 方法重载 </h4>
<p>介绍略。</p>
<h5 id="1-基于-const-的重载">1. 基于 const 的重载 </h5>
<p>可根据 const 重载方法。也就是说，可以编写两个名称相同、参数也相同的方法，其中一个是 const，另一个不是。如果是 const 对象，就调用 const 方法；如果是非 const 对象，就调用非 const 方法。</p>
<p>通常情况下，const 版本和非 const 版本的实现是一样的。为避免代码重复，可使用 Scott Meyer 的 const_cast 模式。例如，Spreadsheet 类有一个 getCellAt() 方法，该方法返回 SpreadsheetCell 的非 const 引用。可添加 const 重载版本，它返回 SpreadsheetCell 的 const 引用。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Spreadsheet</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    Spreadsheet<span class="token operator">&amp;</span>       <span class="token function">getCellAt</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-const">const</span> Spreadsheet<span class="token operator">&amp;</span> <span class="token function">getCellAt</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>对于 Scott Meyer 的 <em><strong>const_cast()</strong></em> 模式，可像往常一样实现 const 版本，此后通过适当转换，传递对 const 版本的调用，以实现非 const 版本。如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-const">const</span> Spreadsheet<span class="token operator">&amp;</span> <span class="token class-name">Spreadsheet</span><span class="token double-colon punctuation">::</span><span class="token function">getCellAt</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span>
<span class="token punctuation">{</span>
    <span class="token function">verifyCoordinate</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-return">return</span> m_cells<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">[</span>y<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

Spreadsheet<span class="token operator">&amp;</span> <span class="token class-name">Spreadsheet</span><span class="token double-colon punctuation">::</span><span class="token function">getCellAt</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> <span class="token generic-function"><span class="token function">const_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span>SpreadsheetCell<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token function">as_const</span><span class="token punctuation">(</span><span class="token operator">*</span><span class="token keyword keyword-this">this</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getCellAt</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>使用 <em><strong>std::as_const()</strong></em>（在 &lt;utility&gt; 中定义）将 <em><strong>*this</strong></em> 转换为 const Spreadsheet&amp;，调用 getCellAt() 的 const 版本，它返回 const SpreadsheetCell&amp;，然后使用 <em><strong>const_cast()</strong></em>，将其转换为非 const 的 SpreadsheetCell&amp;。</p>
<p>有了这两个重载的 getCellAt()，现在可在 const 和非 const 的 Spreadsheet 对象上调用 getCellAt()。</p>
<p>这种方法可以省去很多代码。</p>
<h5 id="2-显式删除重载">2. 显式删除重载 </h5>
<p>重载方法可被显式删除，可以用这种方法禁止调用具有特定参数的成员函数。示例略。</p>
<h5 id="3-引用限定方法">3. 引用限定方法 </h5>
<p>可以对类的非临时和临时实例调用普通类方法。假设有以下类：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">TextHolder</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">TextHolder</span><span class="token punctuation">(</span>string text<span class="token punctuation">)</span>
        <span class="token operator">:</span> m_text<span class="token punctuation">{</span><span class="token function">move</span><span class="token punctuation">(</span>text<span class="token punctuation">)</span><span class="token punctuation">}</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-const">const</span> string<span class="token operator">&amp;</span> <span class="token function">getText</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token punctuation">{</span> <span class="token keyword keyword-return">return</span> m_text<span class="token punctuation">;</span> <span class="token punctuation">}</span>

<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    string m_text<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>毫无疑问，可以在 TextHolder 的非临时实例上调用 getText()方法。例如：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>TextHolder textHolder<span class="token punctuation">{</span><span class="token string">"Hello World"</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
cout <span class="token operator">&lt;&lt;</span> textHolder<span class="token punctuation">.</span><span class="token function">getText</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
</code></pre><p>然而，getText() 也可被临时实例调用。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>cout <span class="token operator">&lt;&lt;</span> TextHolder<span class="token punctuation">{</span><span class="token string">"Hello World"</span><span class="token punctuation">}</span><span class="token punctuation">.</span><span class="token function">getText</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
cout <span class="token operator">&lt;&lt;</span> <span class="token function">move</span><span class="token punctuation">(</span>textHolder<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getText</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
</code></pre><p>可以显式指定能够调用某个方法的实例类型，无论是临时实例还是非临时实例。这是通过向方法添加一个所谓的<strong>引用限定符（ref-qualifier）</strong> 来实现的。如果只应在非临时实例上调用方法，则在方法头之后添加一个 <em><strong>&amp;</strong></em> 限定符。类似的，如果只应在临时实例上调用方法，则要添加一个 <em><strong>&amp;&amp;</strong></em> 限定符。</p>
<p>下面修改的 TextHolder 类通过对返回 m_text 的 const 引用来实现 &amp; 限定的 getText()。另一方面，&amp;&amp; 限定的 getText() 返回对 m_text 的右值引用，以便可以将 m_text 移出 TextHolder。例如，如果希望从临时 TextHolder 实例检索文本，则着可能更有效。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">TextHolder</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">TextHolder</span><span class="token punctuation">(</span>string text<span class="token punctuation">)</span>
        <span class="token operator">:</span> m_text<span class="token punctuation">{</span><span class="token function">move</span><span class="token punctuation">(</span>text<span class="token punctuation">)</span><span class="token punctuation">}</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-const">const</span> string<span class="token operator">&amp;</span> <span class="token function">getText</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token operator">&amp;</span> <span class="token punctuation">{</span> <span class="token keyword keyword-return">return</span> m_text<span class="token punctuation">;</span> <span class="token punctuation">}</span>
    string<span class="token operator">&amp;&amp;</span>      <span class="token function">getText</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">{</span> <span class="token keyword keyword-return">return</span> <span class="token function">move</span><span class="token punctuation">(</span>m_text<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    string m_text<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>假设有以下调用：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>TextHolder textHolder<span class="token punctuation">{</span><span class="token string">"Hello World"</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
cout <span class="token operator">&lt;&lt;</span> textHolder<span class="token punctuation">.</span><span class="token function">getText</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>

cout <span class="token operator">&lt;&lt;</span> TextHolder<span class="token punctuation">{</span><span class="token string">"Hello World"</span><span class="token punctuation">}</span><span class="token punctuation">.</span><span class="token function">getText</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
cout <span class="token operator">&lt;&lt;</span> <span class="token function">move</span><span class="token punctuation">(</span>textHolder<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getText</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
</code></pre><p>第一个对 getText() 的调用使用了 &amp; 限定符重载，第二个和第三个使用了 &amp;&amp; 限定符重载。</p>
<h4 id="934-内联方法">9.3.4 内联方法 </h4>
<p>（粤嵌老师陈景佳讲的挺详细了，略吧）（<s>半年后回来看已经忘了</s>）</p>
<p>如果编写了内联方法，应该将方法与其所在的类的定义放在统一文件中。</p>
<blockquote>
<p>高级 C++编译器不要求把内联方法的定义和类定义放在同个文件中。例如，Microsoft Visual C++ 支持链接时代码生成，会自动将较小的函数内联，哪怕这些函数没有声明为内联的或者没有在头文件中定义，也同样如此。GCC 和 Clang 具有类似的功能。</p>
</blockquote>
<h4 id="935-默认参数">9.3.5 默认参数 </h4>
<p>C++ 中，默认参数与方法重载类似。在原型中可为函数或方法指定默认值。如果用户指定了这些参数，默认值会被忽略；如果用户忽略的这些参数，将会使用默认值。</p>
<p>但是存在一个限制：只能从<strong>最右边</strong>的参数开始提供<strong>连续</strong>的默认参数列表，否则编译器将无法用默认参数匹配缺失的参数。默认参数可用于函数，方法和构造函数。</p>
<p>所有参数都有默认值的构造函数等同于默认构造函数。如果试图同时声明默认构造函数，以及具有多个参数并且所有参数都有默认值的构造函数，编译器会报错。</p>
<h3 id="94-不同的数据成员类型">9.4 不同的数据成员类型 </h3>
<p>C++ 为数据成员提供了多种选择。除了在类中简单的声明数据成员外，还可创建 static 数据成员（类的所有对象共享）、const 数据成员、引用数据成员、const 引用数据成员和其他成员。本节解释这些不同类型的数据成员。</p>
<h4 id="941-静态数据成员">9.4.1 静态数据成员 </h4>
<p>有时让类的所有对象都包含某个变量的副本是没必要的。数据成员可能只对类有意义，而每个对象都拥有其副本是不合适的。C++用 <em><strong>static</strong></em> 数据成员解决了这个问题。static 数据成员属于类但并<strong>不是对象的数据成员</strong>，可将 static 数据成员当作类的全局变量。如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Spreadsheet</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    <span class="token keyword keyword-static">static</span> size_t ms_counter<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>不仅要在类定义中列出 static 类成员，还需要再源文件中为其<strong>分配内存</strong>，通常是定义类方法的那个<strong>源文件</strong>。在此还可初始化 static 成员，但注意与普通的变量和数据成员不同，默认情况下它们会初始化为 0。static 指针会初始化为 nullptr。下面是为 ms_counter 分配空间并初始化为 0 的代码：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>size_t Spreadsheet<span class="token double-colon punctuation">::</span>ms_counter<span class="token punctuation">;</span>
</code></pre><p>静态数据成员默认情况下初始化为 0，但如果需要，可将它们显式的初始化为 0，如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>size_t Spreadsheet<span class="token double-colon punctuation">::</span>ms_counter<span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>这行代码在函数或方法<strong>外部</strong>，与声明全局变量非常类似。</p>
<ol>
<li>内联变量<br>
略了哈，使用 inline 关键字</li>
<li>在类方法内访问静态数据成员<br>
在类方法内部，可以像使用普通数据成员那样使用静态数据成员。示例略。</li>
<li>在方法外访问静态数据成员<br>
访问控制限定符适用于 static 数据成员。略略略</li>
</ol>
<h4 id="942-const-static-数据成员">9.4.2 const static 数据成员 </h4>
<p>类中的数据成员可声明为 const，意味着在创建并初始化后，数据成员的值不能再改变。如果某个常量只适用于类，应该使用 static const（或 const static）数据成员，也成为类常量，而不是全局常量。可在类定义中定义和初始化整形和枚举类型的 static const 数据成员，而不需要将其指定为内联变量。</p>
<h4 id="943-引用数据成员">9.4.3 引用数据成员 </h4>
<p>示例就略了。</p>
<p>如果在类中定义了一个引用成员，如果不指向某些事物，引用将无法存在，因此在构造函数的初始化器中必须给这个成员指定一个值。</p>
<p>在拷贝构造函数中也必须初始化这个引用成员。默认拷贝构造函数会委托给非拷贝构造函数（初始化引用成员），因此将自动处理。</p>
<p>记住，在初始化一个引用后，不能改变它指向对象，因此无法在赋值运算符中对引用赋值。这意味着根据使用情形，可能无法为具有引用数据成员的类提供赋值运算符。如果属于这种情况，通常将<strong>赋值运算符标记为删除</strong>。</p>
<p>最后，引用数据成员也可被标记为 const。</p>
<h3 id="95-嵌套类">9.5 嵌套类 </h3>
<p>类定义不仅可包含成员函数和数据成员，还可编写嵌套类和嵌套结构体、声明类型别名或者创建枚举类型。类中声明的一切内容都具有类作用域。如果声明的内容是 public 的，那么可在类外使用 Class:: 作用域解析语法访问。</p>
<p>可在类的定义中提供另一个类定义。示例就略了。</p>
<p>普通的访问控制也适用于嵌套类定义。如果声明了一个 private 或 protected 嵌套类，这个类只能在外围类（outer class，即包含它的类）中使用。嵌套的类有权访问外围类中的<strong>所有 private 或 protected 成员</strong>，而外围类却只能访问嵌套类中的 public 成员。</p>
<h3 id="96-类内的枚举类型">9.6 类内的枚举类型 </h3>
<p>枚举类型也可作为类的数据成员。示例就略了。</p>
<h3 id="97-运算符重载">9.7 运算符重载 </h3>
<h4 id="971-示例为-spreadsheetcell-实现加法">9.7.1 示例：为 SpreadsheetCell 实现加法 </h4>
<p>具体去看书，这里只抄一些大概</p>
<h5 id="1-首次尝试add-加法">1. 首次尝试：add 加法 </h5>
<p>可像下面这样声明并定义 SpreadsheetCell 类的 add() 方法</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">SpreadsheetCell</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    SpreadsheetCell <span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> cell<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>这个方法将两个单元格相加，返回第三个新的单元格，其值为前两个单元格的和。将这个方法声明为 const，并把 const SpreadsheetCell 的引用作为参数，原因是 add() 不改变任意一个原始单元格。实现略。</p>
<h5 id="2-第二次尝试将-operator-作为方法重载">2. 第二次尝试：将 operator+ 作为方法重载 </h5>
<p>C++ 允许编写自己的加号版本，以正确地处理类，称为加运算符。为此可编写一个名为 operator+ 地方法，如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">SpreadsheetCell</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    SpreadsheetCell <span class="token keyword keyword-operator">operator</span><span class="token operator">+</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> cell<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>实现略。</p>
<p>operator+ 只是个名称，就像 foo 或 add 一样。当C++编译器分析一个程序，遇到运算符（例如，+、-、= 或 &lt;&lt;）时，就会试着查找名为 operator+、operator-、operator= 或 operator&lt;&lt;，且具有适当参数的函数或方法。</p>
<p>注意，用作 operator+ 参数的对象类型并不一定要与编写 operator+ 的类相同。</p>
<p>此外还要注意，可任意指定 operator+ 的返回值类型。但应该遵循最不让人诧异的原则，也就是说，operator+ 的返回值通常应该是用户期望的。</p>
<h6 id="隐式转换">隐式转换 </h6>
<p>一旦编写前面所示的 operator+，不仅可将两个单元格相加，还可将单元格与 string_view、double 或 int 值相加。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>SpreadsheetCell myCell<span class="token punctuation">{</span><span class="token number">4</span><span class="token punctuation">}</span><span class="token punctuation">,</span> aThirdCell<span class="token punctuation">;</span>
string          str<span class="token punctuation">{</span><span class="token string">"hello"</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
aThirdCell <span class="token operator">=</span> myCell <span class="token operator">+</span> string_view<span class="token punctuation">{</span>str<span class="token punctuation">}</span><span class="token punctuation">;</span>
aThirdCell <span class="token operator">=</span> myCell <span class="token operator">+</span> <span class="token number">5.6</span><span class="token punctuation">;</span>
aThirdCell <span class="token operator">=</span> myCell <span class="token operator">+</span> <span class="token number">4</span><span class="token punctuation">;</span>
</code></pre><p>上面的代码之所以可运行，是应为编译器会试着查找合适的 operator+，而不是只查找指定类型的那个 operator+。为找到 operator+，编译器还试图查找合适的<strong>类型转化</strong>。SpreadsheetCell 类拥有<strong>转换构造函数</strong>（见第 8 章），可以将 double 或 string_view 转换为 SpreadsheetCell。在上例中当编译器看到 SpreadsheetCell 试图与 double 值相加时，发现了用 double 值作为参数的 SpreadsheetCell 构造函数，就会构建一个<strong>临时</strong>的 SpreadsheetCell 对象，传递给 operator+。</p>
<p>由于必须创建临时对象，隐式使用构造函数的效率不高。为避免与 double 值相加时隐式地使用构造函数，可编写第二个 operator+。</p>
<h5 id="3-第三次尝试全局-operator">3. 第三次尝试：全局 operator+ </h5>
<p>隐式转换允许使用 operator+ 方法将 SpreadsheetCell 对象与 int 和 double 值相加。然而，这个运算符不具有互换性。</p>
<p>当 SpreadsheetCell 对象在运算符的左边时，隐式转换正常运行，但在右边时无法运行。加法时可交换的，因此这里存在错误。问题在于必须在 SpreadsheetCell 对象上调用 operator+ 方法，对象必须在 operator+ 的左边。这是C++语言定义的方式。</p>
<p>然而，如果用不局限于某个特定对象的<strong>全局 operator+ 函数</strong>替换类内的 operator+ 方法，就可以正常运行。</p>
<h4 id="972-重载算术运算符">9.7.2 重载算术运算符 </h4>
<p>理解了如何编写 operator+，剩余的基本算术运算符就变得简单了，+、- 和 * 的声明就略了，提一下 /。</p>
<p>对于 operator/ 而言，唯一棘手指出是记着检查除数是否为 0，如果检测到除数为 0，该方法将抛出异常。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>SpreadsheetCell <span class="token keyword keyword-operator">operator</span><span class="token operator">/</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> lhs<span class="token punctuation">,</span>   <span class="token comment">//</span>
                          <span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>rhs<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-throw">throw</span> invalid_argument<span class="token punctuation">{</span><span class="token string">"Divide by zero."</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword keyword-return">return</span> SpreadsheetCell<span class="token punctuation">{</span>lhs<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">/</span> rhs<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h5 id="重载简写算术运算符">重载简写算术运算符 </h5>
<p>除了基本算术运算符外，C++还提供了简写运算符，例如 += 和 -=。必须<strong>显式的重载简写运算符</strong>。这些运算符与基本运算符不同，它们会改变运算符左边的对象，而不是创建一个新对象。此外还有一个微妙差别，它们生成的结果是对<strong>被修改对象的引用</strong>，这一点与赋值运算符相似。</p>
<p>简写算术运算符的左边总要有一个对象，因此应该将其<strong>作为方法</strong>而不是全局函数。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">SpreadsheetCell</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    SpreadsheetCell<span class="token operator">&amp;</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">+=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span><span class="token punctuation">;</span>
    SpreadsheetCell<span class="token operator">&amp;</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">-=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span><span class="token punctuation">;</span>
    SpreadsheetCell<span class="token operator">&amp;</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">*=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span><span class="token punctuation">;</span>
    SpreadsheetCell<span class="token operator">&amp;</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">/=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>下面是 operator+= 的实现，其他的与此类似</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>SpreadsheetCell<span class="token operator">&amp;</span> SpreadsheetCell<span class="token double-colon punctuation">::</span><span class="token keyword keyword-operator">operator</span><span class="token operator">+=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token function">set</span><span class="token punctuation">(</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> rhs<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-return">return</span> <span class="token operator">*</span><span class="token keyword keyword-this">this</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>简写算术运算符是对基本算术运算符和赋值运算符的结合。</p>
<p>如果既有某个运算符的普通版本，又有简写版本，建议<strong>基于简写版本实现普通版本</strong>，以避免代码重复。例如：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>SpreadsheetCell <span class="token keyword keyword-operator">operator</span><span class="token operator">+</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> lhs<span class="token punctuation">,</span>   <span class="token comment">//</span>
                          <span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-auto">auto</span> result<span class="token punctuation">{</span>lhs<span class="token punctuation">}</span><span class="token punctuation">;</span>   <span class="token comment">// 拷贝左值</span>
    result <span class="token operator">+=</span> rhs<span class="token punctuation">;</span>
    <span class="token keyword keyword-return">return</span> result<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h4 id="973-重载比较运算符">9.7.3 重载比较运算符 </h4>
<p>在 C++20 标准之后，比较运算符有了很多变化。</p>
<p>与基本算术运算符一样，6 个 C++20 之前的比较运算符应该是全局函数，这样就可以在运算符的左侧和右侧参数上使用隐式转换。比较运算符都返回 bool。当然，也可以更改返回类型，但不建议这样做。</p>
<p>6 个基本比较运算符为：<em><strong>==</strong></em>、<em><strong>&lt;</strong></em>、<em><strong>&gt;</strong></em>、<em><strong>!=</strong></em>、<em><strong>&lt;=</strong></em> 和 <em><strong>&gt;=</strong></em>。示例略。</p>
<p>当类中的数据成员较多时，比较每个数据成员可能比较痛苦。然而，当实现了 == 和 &lt; 之后，可以根据这两个运算符编写其他比较运算符。例如：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-bool">bool</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">&gt;=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> lhs<span class="token punctuation">,</span> <span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> <span class="token operator">!</span><span class="token punctuation">(</span>lhs <span class="token operator">&lt;</span> rhs<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>用这种方式，需要编写 6 个单独的函数来支持 7 个比较运算符，这只是为了相互比较两个 SpreadsheetCell。使用当前实现的 6 个比较函数，可以将 SpreadsheetCell 与 double 进行比较，因为 double 参数隐式转换为 SpreadsheetCell。如前所述，这种隐式转换可能效率低下，因为必须创建临时对象。与前面的 operator+ 一样，可以通过显式实现与 double 进行比较的函数来避免这种情况。对于每个操作符&lt;op&gt;，将需要多个重载。</p>
<p>如果需要支持所有的比较运算符，这将写很多重读的代码。</p>
<p>现在切换一下，C++20 简化了向类中添加对比较运算符的支持。首先，对于 C++20，实际上建议将operator== 实现为类的成员函数，而不是全局函数。还要注意，添加 <em><strong>[[nodiscard]]</strong></em> 属性是个好主意，这样操作符的结果就不会被忽略。例如：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token punctuation">[</span><span class="token punctuation">[</span>nodiscard<span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token keyword keyword-bool">bool</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">==</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>
</code></pre><p>在 C++20 中，单个的 operator-- 重载就可以使下面的比较生效。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>myCell <span class="token operator">==</span> <span class="token number">10</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    cout <span class="token operator">&lt;&lt;</span> <span class="token string">"myCell == 10\n"</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span><span class="token number">10</span> <span class="token operator">==</span> myCell<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    cout <span class="token operator">&lt;&lt;</span> <span class="token string">"10 == myCell\n"</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>诸如 10==myCell 的表达式由 C++20 编译器重写为 myCell==10，可以为其调用 operator== 成员函数。此外，通过实现 operator==，C++20 会自动添加对 != 的支持。</p>
<p>接下来，要实现对全套比较运算符的支持，在 C++20 中，只需要实现一个额外的重载运算符，<mark>operator&lt;=&gt;</mark>。一旦类由运算符 == 和 &lt;=&gt; 的重载，C++20 就会自动为所有 6 个比较运算符提供支持！对于 SpreadsheetCell 类，运算符 &lt;=&gt; 如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token punctuation">[</span><span class="token punctuation">[</span>nodiscard<span class="token punctuation">]</span><span class="token punctuation">]</span> std<span class="token double-colon punctuation">::</span>partial_ordering   <span class="token comment">//</span>
<span class="token keyword keyword-operator">operator</span><span class="token operator">&lt;=&gt;</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> SpreadsheetCell <span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>
</code></pre><blockquote>
<p>注意，C++20 编译器不会根据 &lt;=&gt; 重写 == 或 !=。这样做是为了避免性能问题，因为 operator== 的显式实现通常比使用 &lt;=&gt; 更高效。</p>
</blockquote>
<p>存储在 SpreadsheetCell 中的值是 double 类型的。请记住，第 1 章提到过，浮点类型只有偏序，这就是重载返回 std::partial_ordering 的原因。实现非常简单:</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>partial_ordering SpreadsheetCell<span class="token double-colon punctuation">::</span><span class="token keyword keyword-operator">operator</span><span class="token operator">&lt;=&gt;</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> <span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;=&gt;</span> rhs<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>通过实现 operator&lt;=&gt;，C++20 可以自动提供对&gt;、&lt;、&lt;= 和 &gt;= 的支持，通过将使用这些运算符的表达式重写为使用 &lt;=&gt; 的。例如，诸如 myCell&lt;aThirdCell 的表达式会自动重写为与 std::is_lt(myCell&lt;=&gt;aThirdCell) 等价的内容，其中 is_lt() 是一个命名的比较函数，见第 1 章。</p>
<p>所以，只需要实现 operator== 和 operator&lt;=&gt;，SpreadsheetCell 类就可以支持所有的比较运算符。</p>
<p>由于 SpreadsheetCell 类支持从 double 到 SpreadsheetCell 的隐式转换，因此也支持以下比较：</p>
<p>如果可以显式地将 operator&lt;=&gt; 设置为默认，作者建议这样做，而不是自己实现它。通过编译器为你编写，它将与新添加或修改地数据成员保持同步。如果自己实现运算符，则无论何时添加数据成员或更改现有数据成员，都需要记住更新 operator&lt;=&gt; 的实现。如果编译器没有自动生成 operator==，则以上内容对其也适用。</p>
<p>只有当 operator== 和 &lt;=&gt; 使用定义操作符的类类型的 const 引用作为参数时，才可能显式将 operator== 和 &lt;=&gt; 设置为默认。例如，以下操作不起作用：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token punctuation">[</span><span class="token punctuation">[</span>nodiscard<span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token keyword keyword-auto">auto</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">&lt;=&gt;</span><span class="token punctuation">(</span><span class="token keyword keyword-double">double</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
</code></pre><blockquote>
<p><strong>注意：</strong><br>
在C++2- 中为了添加所有 6 个不计较运算符的支持：</p>
<ul>
<li>
<p>如果一个默认的 operator&lt;=&gt; 适用于你的类，那么只需要一行代码来显式地将默认运算符 &lt;=&gt; 作为一个方法。在某些情况下，你可能还需要显式地将 operator== 设置为默认。</p>
</li>
<li>
<p>否则，需要作为方法重载 operator== 和 &lt;=&gt; 地实现<br>
不需要手动实现其他的比较运算符</p>
</li>
</ul>
</blockquote>
<h4 id="974-创建具有运算符重载的类型">9.7.4 创建具有运算符重载的类型 </h4>
<p>略。</p>
<h3 id="98-创建稳定的接口">9.8 创建稳定的接口 </h3>
<p>理解了在 C++ 中编写类的所有语法后，回顾第 5 章和第 6 章的设计原则会对此有所帮助。在C++ 中，类是主要的抽象单元，应将<strong>抽象原则</strong>应用到类，尽可能分离接口和实现。确切来说，应该<strong>将所有数据成员设置为 private</strong>，并提供相应的<strong>获取器</strong>和<strong>设置器</strong>方法。</p>
<h4 id="使用接口类和实现类">使用接口类和实现类 </h4>
<p>注意，书中使用了 C++20 的模块，我想试着把它改为不用模块的版本。todo…</p>
<p>即使提前进行估算并采用最佳设计原则，C++ 语言本质上对抽象原则也并不友好。其语法要求将 public 接口和 private（或 protected）数据成员及方法放在一个类定义中，从而将类的某些内部实现细节向客户公开。这种做法的缺点在于，如果不得不在类中加入新的非 public 方法或数据成员，所有的客户代码都必须重新编译，对于较大项目而言这是负担。</p>
<p>可创建清晰的接口，并隐藏所有实现细节，从而得到稳定的接口。但这样做有点繁杂。</p>
<p>基本原则是为想编写的每个类都定义两个类：<strong>接口类</strong>和<strong>实现类</strong>。实现类于不采用这种方法编写的类相同，接口类给出了<strong>与实现类一样的 public 方法</strong>，但只有一个数据成员：<strong>指向实现类对象的一个指针</strong>。这称为 <em><strong>pimpl idiom</strong></em>（<strong>私有实现惯用方法</strong>，也称为 <mark><strong>柴郡猫技术</strong></mark> ）或 bridge 模式，接口类方法的实现只是调用实现类对象的等价方法。这样做的结果是无论实现如何改变，都不会影响 public 接口类，从而降低了重新编译的可能。当有且只有实现改变时，使用接口类的客户并不需要重新编译。注意只有在单个数据成员是实现类的指针时，这个方法才有效。如果它是按值传递的数据成员，在实现类的定义改变时，客户代码必须重新编译。</p>
<p>以下将方法应用到 Spreadsheet 类，这是头文件</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">ifndef</span> <span class="token expression">SPREADSHEET_H</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">SPREADSHEET_H</span></span>


<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;cstddef&gt;</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;memory&gt;</span></span>

<span class="token comment">// using namespace std;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">SpreadsheetCell</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">SpreadsheetApplication</span>
<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Spreadsheet</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-static">static</span> <span class="token keyword keyword-const">const</span> size_t MaxHeight<span class="token punctuation">{</span><span class="token number">100</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-static">static</span> <span class="token keyword keyword-const">const</span> size_t MaxWidth<span class="token punctuation">{</span><span class="token number">100</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    <span class="token keyword keyword-class">class</span> <span class="token class-name">Impl</span><span class="token punctuation">;</span>
    std<span class="token double-colon punctuation">::</span>unique_ptr<span class="token operator">&lt;</span>Impl<span class="token operator">&gt;</span> m_impl<span class="token punctuation">;</span>


<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">Spreadsheet</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> SpreadsheetApplication<span class="token operator">&amp;</span> theApp<span class="token punctuation">,</span> size_t width <span class="token operator">=</span> MaxWidth<span class="token punctuation">,</span>
                size_t height <span class="token operator">=</span> MaxHeight<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">Spreadsheet</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Spreadsheet<span class="token operator">&amp;</span> src<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">Spreadsheet</span><span class="token punctuation">(</span>Spreadsheet<span class="token operator">&amp;&amp;</span><span class="token punctuation">)</span> <span class="token keyword keyword-noexcept">noexcept</span><span class="token punctuation">;</span>
    <span class="token operator">~</span><span class="token function">Spreadsheet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    Spreadsheet<span class="token operator">&amp;</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Spreadsheet<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span><span class="token punctuation">;</span>
    Spreadsheet<span class="token operator">&amp;</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span>Spreadsheet<span class="token operator">&amp;&amp;</span><span class="token punctuation">)</span> <span class="token keyword keyword-noexcept">noexcept</span><span class="token punctuation">;</span>

    <span class="token keyword keyword-void">void</span>             <span class="token function">setCellAt</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">,</span> <span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> cell<span class="token punctuation">)</span><span class="token punctuation">;</span>
    SpreadsheetCell<span class="token operator">&amp;</span> <span class="token function">getCellAt</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span><span class="token punctuation">;</span>

    size_t <span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>

    <span class="token keyword keyword-void">void</span> <span class="token function">swap</span><span class="token punctuation">(</span>Spreadsheet<span class="token operator">&amp;</span> other<span class="token punctuation">)</span> <span class="token keyword keyword-noexcept">noexcept</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-void">void</span> <span class="token function">swap</span><span class="token punctuation">(</span>Spreadsheet<span class="token operator">&amp;</span> first<span class="token punctuation">,</span> Spreadsheet<span class="token operator">&amp;</span> second<span class="token punctuation">)</span> <span class="token keyword keyword-noexcept">noexcept</span><span class="token punctuation">;</span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>
</code></pre><p>实现类 Impl 是一个 private 嵌套类，因为只有 Spreadsheet 需要用到这个实现类。Spreadsheet 现<strong>只包含一个数据成员</strong>：指向 Impl 实例的指针。public 方法与原来的 Spreadsheet 相同。</p>
<p>嵌套的 Spreadsheet::Impl 类定义在 spreadsheet 实现文件中。Impl 类不应该导出，因为它应该<strong>对用户隐藏起来</strong>。Spreadsheet.cpp 实现文件的开头如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">"Spreadsheet.h"</span></span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">"SpreadsheetCell.h"</span></span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;algorithm&gt;</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;format&gt;</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;stdexcept&gt;</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;utility&gt;</span></span>

<span class="token keyword keyword-using">using</span> <span class="token keyword keyword-namespace">namespace</span> std<span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Spreadsheet</span><span class="token operator">:</span><span class="token base-clause"><span class="token operator">:</span><span class="token class-name">Impl</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    <span class="token keyword keyword-const">const</span> size_t      m_id<span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    size_t            m_width<span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    size_t            m_height<span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    SpreadsheetCell<span class="token operator">*</span><span class="token operator">*</span> m_cells<span class="token punctuation">{</span><span class="token keyword keyword-nullptr">nullptr</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token keyword keyword-const">const</span> SpreadsheetApplication<span class="token operator">&amp;</span> m_theApp<span class="token punctuation">;</span>

    <span class="token keyword keyword-static">static</span> <span class="token keyword keyword-inline">inline</span> size_t ms_counter<span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">}</span><span class="token punctuation">;</span>



<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">Impl</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> SpreadsheetApplication<span class="token operator">&amp;</span> theApp<span class="token punctuation">,</span> size_t width<span class="token punctuation">,</span> size_t height<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">Impl</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Impl<span class="token operator">&amp;</span> src<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">Impl</span><span class="token punctuation">(</span>Impl<span class="token operator">&amp;&amp;</span><span class="token punctuation">)</span> <span class="token keyword keyword-noexcept">noexcept</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token operator">~</span><span class="token function">Impl</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    Impl<span class="token operator">&amp;</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Impl<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span><span class="token punctuation">;</span>
    Impl<span class="token operator">&amp;</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span>Impl<span class="token operator">&amp;&amp;</span><span class="token punctuation">)</span> <span class="token keyword keyword-noexcept">noexcept</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>

    <span class="token keyword keyword-void">void</span>             <span class="token function">setCellAt</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">,</span> <span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> cell<span class="token punctuation">)</span><span class="token punctuation">;</span>
    SpreadsheetCell<span class="token operator">&amp;</span> <span class="token function">getCellAt</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span><span class="token punctuation">;</span>

    size_t <span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>

    <span class="token keyword keyword-void">void</span> <span class="token function">swap</span><span class="token punctuation">(</span>Impl<span class="token operator">&amp;</span> other<span class="token punctuation">)</span> <span class="token keyword keyword-noexcept">noexcept</span><span class="token punctuation">;</span>

<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    <span class="token keyword keyword-void">void</span> <span class="token function">verifyCoordinate</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

Spreadsheet<span class="token double-colon punctuation">::</span><span class="token class-name">Impl</span><span class="token double-colon punctuation">::</span><span class="token function">Impl</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> SpreadsheetApplication<span class="token operator">&amp;</span> theApp<span class="token punctuation">,</span> size_t width<span class="token punctuation">,</span> size_t height<span class="token punctuation">)</span>
    <span class="token operator">:</span> m_id<span class="token punctuation">{</span>ms_counter<span class="token operator">++</span><span class="token punctuation">}</span>
    <span class="token punctuation">,</span> m_width<span class="token punctuation">{</span><span class="token function">min</span><span class="token punctuation">(</span>width<span class="token punctuation">,</span> Spreadsheet<span class="token double-colon punctuation">::</span>MaxWidth<span class="token punctuation">)</span><span class="token punctuation">}</span>
    <span class="token punctuation">,</span> m_height<span class="token punctuation">{</span><span class="token function">min</span><span class="token punctuation">(</span>height<span class="token punctuation">,</span> Spreadsheet<span class="token double-colon punctuation">::</span>MaxHeight<span class="token punctuation">)</span><span class="token punctuation">}</span>
    <span class="token punctuation">,</span> m_theApp<span class="token punctuation">{</span>theApp<span class="token punctuation">}</span>
<span class="token punctuation">{</span>
    m_cells <span class="token operator">=</span> <span class="token keyword keyword-new">new</span> SpreadsheetCell<span class="token operator">*</span><span class="token punctuation">[</span>m_width<span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-for">for</span> <span class="token punctuation">(</span>size_t i<span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">}</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> m_width<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        m_cells<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword keyword-new">new</span> SpreadsheetCell<span class="token punctuation">[</span>m_height<span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment">// 其余省略</span>
</code></pre><p>Impl 类具有与 Spreadsheet 类几乎相同的接口。对于方法实现，需要记住 Impl 是一个嵌套类，因此，需要将作用域指定为 Spreadsheet::Impl。因此，对于构造函数，它变成了 Spreadsheet::Impl::Impl(…)。</p>
<p>现在，Spreadsheet 类有一个指向实现类的 unique_ptr，Spreadsheet 类需要一个用户声明的析构函数。我们<strong>不需要对这个析构函数进行任何处理</strong>，可在文件中设置 <em><strong>=default</strong></em>，如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token class-name">Spreadsheet</span><span class="token double-colon punctuation">::</span><span class="token operator">~</span><span class="token function">Spreadsheet</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
</code></pre><p>事实上，它<strong>必须在实现文件中而不是直接在类定义中默认</strong>。原因是 Impl 类仅在 Spreadsheet 类定义中声明，也就是说，编译器知道某个地方将有一个 Spreadsheet::Impl 类，但此时它还不知道定义。因此，不能在类定义中设置默认析构函数，因为这样编译器将尝试使用尚未定义的 Impl 类的析构函数。在这种情况下，将其他方法设置为默认时也是如此，例如移动构造函数和移动赋值运算符。</p>
<p>Spreadsheet 方法（例如 setCellAt() 和 getCellAt()）的实现只是<strong>将请求传递给底层的 Impl 对象</strong>：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-void">void</span> <span class="token class-name">Spreadsheet</span><span class="token double-colon punctuation">::</span><span class="token function">setCellAt</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">,</span> <span class="token keyword keyword-const">const</span> SpreadsheetCell<span class="token operator">&amp;</span> cell<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    m_impl<span class="token operator">-&gt;</span><span class="token function">setCellAt</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">,</span> cell<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

SpreadsheetCell<span class="token operator">&amp;</span> <span class="token class-name">Spreadsheet</span><span class="token double-colon punctuation">::</span><span class="token function">getCellAt</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> m_impl<span class="token operator">-&gt;</span><span class="token function">getCellAt</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Spreadsheet 的构造函数必须<strong>创建一个新的 Impl 实例</strong>来完成这个任务</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token class-name">Spreadsheet</span><span class="token double-colon punctuation">::</span><span class="token function">Spreadsheet</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> SpreadsheetApplication<span class="token operator">&amp;</span> theApp<span class="token punctuation">,</span> size_t width<span class="token punctuation">,</span> size_t height<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    m_impl <span class="token operator">=</span> <span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span>Impl<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>theApp<span class="token punctuation">,</span> width<span class="token punctuation">,</span> height<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token class-name">Spreadsheet</span><span class="token double-colon punctuation">::</span><span class="token function">Spreadsheet</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Spreadsheet<span class="token operator">&amp;</span> src<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    m_impl <span class="token operator">=</span> <span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span>Impl<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token operator">*</span>src<span class="token punctuation">.</span>m_impl<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>拷贝构造函数看上去有点奇怪，因为需要从源 Spreadsheet 复制底层的 Impl。由于拷贝构造函数采用一个指向 Impl 的引用而不是指针，因此<strong>为了获取对象本身</strong>，必须<strong>对 m_Impl 指针解除引用</strong>，以得到它的对象。</p>
<p>Spreadsheet 赋值运算符必须采用类似方式传递到底层的 Impl 的赋值</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Spreadsheet<span class="token operator">&amp;</span> Spreadsheet<span class="token double-colon punctuation">::</span><span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Spreadsheet<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token operator">*</span>m_impl <span class="token operator">=</span> <span class="token operator">*</span>rhs<span class="token punctuation">.</span>m_impl<span class="token punctuation">;</span>
    <span class="token keyword keyword-return">return</span> <span class="token operator">*</span><span class="token keyword keyword-this">this</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>赋值运算符的第一行看上去有点奇怪。Spreadsheet 赋值运算符需要传递对 Impl 赋值运算符的调用，而这个运算符只有在复制直接对象时才会执行。通过对 m_impl 指针解除引用，会<strong>强制使用直接对象赋值</strong>，从而<strong>调用 Impl 的赋值运算符</strong>。</p>
<p>swap() 函数用于交换单独的数据成员：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-void">void</span> <span class="token class-name">Spreadsheet</span><span class="token double-colon punctuation">::</span><span class="token function">swap</span><span class="token punctuation">(</span>Spreadsheet<span class="token operator">&amp;</span> other<span class="token punctuation">)</span> <span class="token keyword keyword-noexcept">noexcept</span>
<span class="token punctuation">{</span>
    std<span class="token double-colon punctuation">::</span><span class="token function">swap</span><span class="token punctuation">(</span>m_impl<span class="token punctuation">,</span> other<span class="token punctuation">.</span>m_impl<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>真正将接口和实现分离的技术功能强大。应该去适应它。支持这种方法最有力的论据不是将接口分离的美感，而是类的实现改变后答复缩短构建时间。一个类不使用 pimpl idiom 时，对实现类的更改将导致漫长的构建过程。例如，给类定义增加数据成员时，将触发使用这个类的其他所有源文件的重新构建。而使用 pimpl idiom，可以修改实现类的定义，只要公共接口保持不变，就不会触发长时间构建过程。</p>
<h2 id="10-揭秘继承技术">10 揭秘继承技术 </h2>
<h3 id="101-使用继承构建类">10.1 使用继承构建类 </h3>
<h4 id="1012-重写方法">10.1.2 重写方法 </h4>
<p>从某个类继承的主要原因是为了添加或替换功能。在许多情况下，可能需要替换或重写某个方法来修改类的行为。</p>
<h5 id="关键字-virtual">关键字 virtual </h5>
<p>在 C++ 中，重写（override）方法有一点别扭，因为必须使用关键字 virtual。只有基类中声明为 virtual 的方法才能被派生类正确的重写。关键字 virtual 出现在方法声明的开头。</p>
<p>派生类也是如此，如果想进一步在派生类中重写它们，相应的方法也应该标记为 virtual。</p>
<p>示例略。</p>
<h5 id="重写方法的语法">重写方法的语法 </h5>
<p>为了重写某个方法，需要在派生类的定义中重新声明这个方法，就像在基类中声明的那样，但是需要添加关键字 <em><strong>override</strong></em>，并从 派生类中删除关键字 virtual。</p>
<p>示例还是略。</p>
<p>注意，在方法定义中，不需要再方法名前重复使用关键字 virtual。</p>
<p>如果需要的话，可以在重写方法的前面你添加关键字 virtual，但这样做是多余的。</p>
<p>一旦将方法或析构函数标记为 virtual，它们在所有派生类中就一直是 virtual，即使在派生类中删除了 virtual 关键字，也是如此。</p>
<h5 id="客户对重写方法的看法">客户对重写方法的看法 </h5>
<p>示例依然略。</p>
<p>如前所述，指针或引用可指向某个类或其派生类的对象。对象本身“知道”自己所属的类，因此只要这个方法声明为 virtual，就会自动调用对应的方法。如果<strong>在基类中省略了 virtual 关键字，重写功能将无法正确的运行</strong>。</p>
<p>记住，即使基类的引用或指针知道这实际上是一个派生类，也无法访问没有在基类中定义的，定义在派生类中的方法或成员。</p>
<p>非指针或非引用对象无法正确处理派生类的特征信息。可将 Derived 对象转换为 Base 对象，或将 Derived 对象赋值给 Base 对象，因为 Derived 对象也是 Base 对象。然而，此时这个对象将<strong>丢失派生类的所有信息</strong>。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Derived myDerived<span class="token punctuation">;</span>
Base    assignedObject<span class="token punctuation">{</span>myDerived<span class="token punctuation">}</span><span class="token punctuation">;</span>
assignedObject<span class="token punctuation">.</span><span class="token function">somMethod</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>为记住这个看上去有点奇怪的行为，可考虑对象在内存中的状态。将 Base 对象当作占据了一块内存的盒子。Derived 对象是稍微大一点的盒子，因为它拥有 Base 对象的一切，还添加了一点内容。对于指向 Derived 对象的引用或指针，这个盒子并没有变——只是可以用新的方法访问它。然而，如果将 Derived 对象转换为 Base 对象，就会为了适应小的盒子而扔掉 Derived 类中全部的“独有特征”。</p>
<blockquote>
<p><strong>注意：</strong></p>
<p>当基类的指针或引用指向派生类对象时，派生类保留其数据成员和重写的方法。但是通过类型转换将派生类对象转换为基类对象时，就会<strong>丢失其独有特征</strong>。重写方法和数据丢失称为 <mark><strong>截断</strong></mark> （slicing）。</p>
</blockquote>
<h5 id="override-关键字">override 关键字 </h5>
<p>override 关键字的使用时可选的，但强烈推荐使用。如果没有 override 关键字，可能会偶然创建一个新的虚方法，而不是重写基类的方法。</p>
<p>示例就略了。</p>
<h5 id="virtual-的真相">virtual 的真相 </h5>
<p>如果方法不是 virtual，也可以试着重写这个方法，但是这样做会导致微妙的错误。</p>
<h6 id="隐藏而不是重写">隐藏而不是重写 </h6>
<p>下面的代码显示了一个基类和一个派生类，每个类都有一个方法。派生类试图重写基类的方法，但是在基类中没有将这个方法声明为 virtual。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Base</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-void">void</span> <span class="token function">go</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"go() called on Base"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Derived</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Base</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
<span class="token keyword keyword-void">void</span> <span class="token function">go</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"go() cassel on Derived"</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>试着用 Derived 对象调用 go() 方法好像没有问题。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Derived myDerived<span class="token punctuation">;</span>
myDerived<span class="token punctuation">.</span><span class="token function">go</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>正如与其的那样，这个调用的结果是 “go() called on Derived”。然而，由于这个方法不是 virtual，因此实际上<strong>没有被重写</strong>。相反，Derived 类创建了一个新的方法，名称也是 go()，这个方法与 Base 类的 go() 方法完全没有关系。为证实这一点，只需要用 Base 指针或引用调用这个方法。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Derived myDerived<span class="token punctuation">;</span>
Base<span class="token operator">&amp;</span>   ref<span class="token punctuation">{</span>myDerived<span class="token punctuation">}</span><span class="token punctuation">;</span>
red<span class="token punctuation">.</span><span class="token function">go</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>我们可能希望输出的是 “go() called on Derived”，但实际上，输出的是 “go() called on Base”。这是因为 ref 变量是一个Base 类型的引用，因为此处省略了 virtual 关键字。当调用 go() 方法时，只是执行了 Base 类中的 go() 方法。由于不是虚方法，因此不要考虑派生类是否重写了这个方法。</p>
<blockquote>
<p><strong>警告：</strong></p>
<p>试图重写非虚方法将会“隐藏”基类定义的方法，并且重写的这个方法只能在派生类环境中使用。</p>
</blockquote>
<h6 id="如何实现-virtual">如何实现 virtual </h6>
<p>为理解如何避免隐藏方法，需要了解 virtual 关键字的真正作用。C++ 在编译类时，会创建一个包含类中所有方法的二进制对象。在非虚情况下，将控制交给正确方法的代码是硬编码，此时会根据编译器的类型调用对应的方法。这称为静态绑定（static binding），也成为早绑定（early binding）。</p>
<p>如果方法声明为 virtual，会使用名为<strong>虚表</strong>（vtable）的特定内存区域来调用正确的实现。每个具有一个或多个虚方法的类都具有一张虚表，这种类的每个对象都包含指向虚表的指针，这个虚表包含指向虚方法实现的指针。通过这种方法，当使用某个对象调用方法时，指针也进入虚表，然后根据实际的对象类型在运行期执行正确版本的方法。着称为动态绑定（dynamic binging）或晚绑定（late binding）。</p>
<p>示例略</p>
<h6 id="使用-virtual-的理由">使用 virtual 的理由 </h6>
<p>要调用虚方法，程序需要执行一项附加操作，即对指向要执行的适当代码的指针解除引用。在多数情况下这样做会轻微地影响性能。但是，在多数应用程序中，很难察觉到使用虚方法和不使用虚方法所带来的性能上的差别。</p>
<p><strong>析构函数都应该声明为 virtual</strong>。原因是，如果析构函数未声明为 virtual，很容易在销毁对象时不释放内存。唯一允许不把析构函数声明为 virtual 的例外情况是，<strong>类被标记为 final</strong>。</p>
<p>例如，派生类使用的内存在构造函数中动态分配，在析构函数中释放。如果不调用析构函数，这块内存将无法释放。类似的，如果派生类具有一些成员，这些成员在类的实例销毁时自动删除，如 std::unique_ptr，那么如果从未调用析构函数，将不会删除这些成员。</p>
<p>实例略。</p>
<blockquote>
<p><strong>注意：</strong></p>
<p>如果在析构函数中什么都不做，只想把它设置为 virtual，可显式的设置 “<em><strong>=default</strong></em>”</p>
</blockquote>
<p>注意从 C++11 开始，如果类具有用户声明的析构函数，就不会生成赋值构造函数和拷贝运算符。基本上，一旦有了用户声明的析构函数，C++ 的 5 法则就会开始执行。在此类情况下，如果仍然需要编译器生成的复制构造函数、拷贝赋值运算符、移动构造函数和移动赋值运算符，可将它们显式地设置为默认（=default）。</p>
<blockquote>
<p><strong>警告：</strong></p>
<p>除非有特别原因，或者类被标记为 final，否则强烈建议 <mark><strong>将所有方法（包括析构函数、构造函数除外）声明为 virtual</strong></mark> ，构造函数不需要，也无法声明为 virtual，因为在创建对象时没总会明确地指定类。</p>
</blockquote>
<h5 id="禁用重写">禁用重写 </h5>
<p>除了将实体类标记为 final，C++ 还允许将<strong>方法标记为 final</strong>。这意味着无法在派生类中重写这个方法。</p>
<h3 id="103-利用父类">10.3 利用父类 </h3>
<p>编写派生类时，需要知道父类和派生类之间的交互方式。创建顺序、构造函数链和类型转换都是潜在的 bug 来源。</p>
<h4 id="1031-父类构造函数">10.3.1 父类构造函数 </h4>
<p>对象并不是突然建立起来的，创建对象时必须同时创建父类和包含于其中的对象。C++ 定义了如下的创建顺序：</p>
<ol>
<li>如果某个类具有基类，则执行基类的默认构造函数。除非在 ctor-initializer 中调用了基类构造函数，此时调用这个构造函数而不是默认构造函数。</li>
<li>类的非静态数据成员<strong>按照声明的顺序</strong>创建。</li>
<li>执行该类的构造函数</li>
</ol>
<p>可递归使用这些规则。如果有祖父类，祖父类就在父类之前初始化，以此类推。</p>
<p>示例略。</p>
<p>注意，基类的构造函数是自动调用的。C++ 将自动调用父类的默认构造函数（如果存在的话）。如果父类的默认构造函数不存在，或者存在默认构造函数但希望使用其他构造函数，可在构造函数初始化器（constructor initializer）中像初始化数据成员那样链接（chain）构造函数。比如，下面的代码显式了没有默认构造函数的 Base 版本。相关版本的 Derived 必须显式地告诉编译器如何调用 Base 构造函数，否则代码无法编译。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Base</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">Base</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> i<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Derived</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Base</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">Derived</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token operator">:</span> Base<span class="token punctuation">{</span><span class="token number">7</span><span class="token punctuation">}</span>
    <span class="token punctuation">{</span> <span class="token comment">/* 其余省略 */</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>从派生类向基类传递构造函数的参数很正常，毫无问题，但是无法传递数据成员。如果这么做，代码可以编译，但是记住<strong>在调用基类构造函数之后才会初始化数据成员</strong>。如果将数据成员作为参数传递给父类构造函数，数据成员不会初始化。</p>
<blockquote>
<p><strong>警告：</strong></p>
<p>虚方法的行为在构造函数中是不同的，如果派生类重写了基类中的虚方法，从<strong>基类构造函数中调用虚方法</strong>，就会<strong>调用虚方法的基类实现而不是派生类中的重写版本</strong>。</p>
</blockquote>
<h4 id="1032-父类的析构函数">10.3.2 父类的析构函数 </h4>
<p>由于析构函数没有参数，因此始终可自动调用父类的析构安徽念书。析构函数的调用顺序刚好于构造函数相反：</p>
<ol>
<li>调用类的析构函数</li>
<li>销毁类的数据成员，销毁顺序与创建的顺序相反</li>
<li>如果有父类，调用父类的析构函数</li>
</ol>
<p>也可递归使用这些规则。链的最底层成员总是第一个被销毁。示例还是略，但是记住，所有析构函数都应该声明为 virtual，这一点非常重要。</p>
<blockquote>
<p><strong>警告：</strong></p>
<p><strong>将所有析构函数声明为 virtual</strong>！ 编译器生成的默认析构函数不是virtual，因此应该<strong>定义自己（或显式设置为默认）的虚析构函数</strong>，至少在父类中应该这样做。</p>
</blockquote>
<hr>
<blockquote>
<p><strong>警告：</strong></p>
<p>与构造函数一样，在析构函数中调用虚方法时，虚方法的行为将有所不同。如果派生类重写了基类中的虚方法，在基类的析构函数中调用该方法，会执行该方法的基类实现，而不是派生类的重写版本。</p>
</blockquote>
<h4 id="1033-使用父类方法">10.3.3 使用父类方法 </h4>
<p>在派生类中重写方法时，将有效替换原始方法。然而，方法的父类版本任然存在，任然可以使用这些方法。</p>
<p>示例略。反正就是用作用域解析。</p>
<h4 id="1034-向上转型和向下转型">10.3.4 向上转型和向下转型 </h4>
<p>如前所述，对象可转换为父类对象，或者赋值给父类。示例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Derived myDerived<span class="token punctuation">;</span>
Base    myBase<span class="token punctuation">{</span>myDerived<span class="token punctuation">}</span><span class="token punctuation">;</span>   <span class="token comment">// 截断</span>
</code></pre><p>这种情况下会导致截断。然而，如果派生类对基类的指针或引用赋值，则不会产生截断。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Base<span class="token operator">&amp;</span> myBase<span class="token punctuation">{</span>myDerived<span class="token punctuation">}</span><span class="token punctuation">;</span>   <span class="token comment">// 不会截断</span>
</code></pre><p>这是通过基类使用派生类的正确途径，也称为向上转型（upcasting）。这也是让方法和函数使用类的引用而不是直接使用类对象的原因。通过使用引用，派生类在传递时没有发生截断。</p>
<blockquote>
<p><strong>警告：</strong></p>
<p>当向上转型时，<strong>使用基类指针或引用</strong>以避免截断</p>
</blockquote>
<p>将基类转换为派生类也称为向下转型（downcasting），专业的 C++ 程序员不通常赞成这种转换，因为无法保证对象实际上属于派生类，也因为向下转型是不好的设计。示例略。</p>
<p>向下转型有时是必需的，在可控环境中可充分利用这种转换。然而，如果打算进行向下转型，应该使用 <em><strong>dynamic_cast()</strong></em>，以使用对象内建的类型信息，拒绝没有意义的类型转换。这种内建信息通常驻留在虚表中，这意味着 <strong>dynamic_cast() 只能用于具有虚表的对象</strong>，即至少有一个虚编号的对象。如果针对某个指针的 dynamic_cast() 失败，这个指针的值就是 nullptr，而不是指向某个无意义的数据。如果针对对象引用的 dynamic_cast() 失败，将抛出 std::bad_cast 异常。详见本章最后一节。</p>
<p>示例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-void">void</span> <span class="token function">lessPresumptuous</span><span class="token punctuation">(</span>Base<span class="token operator">*</span> base<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    Derived<span class="token operator">*</span> myDerived<span class="token punctuation">{</span><span class="token generic-function"><span class="token function">dynamic_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span>Derived<span class="token operator">*</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>base<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>myDerived <span class="token operator">!=</span> <span class="token keyword keyword-nullptr">nullptr</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// TODO</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>记住，向下转型通常是设计不良的标志。为避免使用向下转型，应当反思，并修改谁。比如，lessPresumptuous() 函数实际上只能用于 Derived 类的对象，因此不应当接受 Base 类的指针，而应接受 Derived 类的指针。这样就不需要向下转型了。如果函数用于从 Base 类继承的不同派生类，而应考虑使用多态性的解决方案，如下所述。</p>
<blockquote>
<p><strong>警告：</strong></p>
<p>仅在必要的情况下才使用向下转型，一定要使用 dynamic_cast()。</p>
</blockquote>
<h3 id="104-继承与多态性">10.4 继承与多态性 </h3>
<p>在理解了派生类与父类的关系后，就可以用最有力的方式使用继承——多态性（polymorphism）。第 5 章种说过，多态性可以互换地使用具有相同父类的对象，并用对象替换父类对象。</p>
<h4 id="1042-设计多态性的电子表格单元格">10.4.2 设计多态性的电子表格单元格 </h4>
<p>电子表格单元格需要可以存储 double 型或 string_view 型的数据成员。为此设计 DoubleSpreadsheetCell 和 StringSpreadsheetCell 类，这两个类地位等同，并有共同的父类 SpreadsheetCell。如下所示</p>
<div class="mermaid">classDiagram
class  SpreadsheetCell
class  DoubleSpreadsheetCell
class  StringSpreadsheetCell

SpreadsheetCell &lt;|-- StringSpreadsheetCell
SpreadsheetCell &lt;|-- DoubleSpreadsheetCell
</div><p>上图所示的设计显示了让 SpreadsheetCell 层次结构具有多态性的方法。由于 DoubleSpreadsheetCell 和 StringSpreadsheetCell 都从统一个父类 SpreadsheetCell 继承，总其他代码的视角，它们是可以互换的。实际上这意味着：</p>
<ul>
<li>两个派生类都支持由基类定义的统一接口（方法集）</li>
<li>使用 SpreadsheetCell 对象的代码可调用接口种的任何方法，而不需要知道着个单元格是 StringSpreadsheetCell 还是 DoubleSpreadsheetCell。</li>
<li>由于虚方法的特殊能力，会根据对象所属的类调用接口中每个方法的正确实例</li>
<li>其他数据结构可通过引用父类类型，包含一组多类型的单元格</li>
</ul>
<h4 id="1043-spreadsheetcell-基类">10.4.3 SpreadsheetCell 基类 </h4>
<p>当设计基类时，应该考虑派生类之间的关系。根据这些关系，看提取共有特性并将其放到父类中。比如，字符串单元格和双精度单元格的共同点在于都包含单个数据块。由于数据来自用户，并最终显示给用户。可以把这个值设置为字符串，并作为字符串来获取。这些行为就是用来组成基类的共享功能。</p>
<h5 id="初次尝试">初次尝试 </h5>
<p>SpreadsheetCell 基类负责定义所有派生类支持的行为。在本例中，所有单元格都需要将值设置为字符串。此外，所有单元格都需要将当前值返回为字符串。基类定义中声明了这些方法，以及显式设置为默认的虚析构函数，但没有数据成员。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">SpreadsheetCell</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">SpreadsheetCell</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span>        <span class="token function">set</span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span>string_view value<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-virtual">virtual</span> std<span class="token double-colon punctuation">::</span>string <span class="token function">getString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>当开始编写这个类的.cpp文件时，很快就会遇到问题。由于电子表格单元的基类既不包含 double 也不包含 string 类型的数据成员，如何实现这个类呢？更宽泛地讲，如何定义这样一个父类，这个父类声明了派生类支持的行为，但是并不定义这些行为的实现。</p>
<p>可能的方法之一是为这些行为实现“什么都不做”的功能。比如，调用 SpreadsheetCell 基类的 set() 方法将没有任何效果，因为基类没有任何成员需要设置。然而这种方法仍然存在问题。理想情况下，基类不应该由实例。调用 set() 方法应该总是有效，因为总是会基于 DoubleSpreadsheetCell 或 StringSpreadsheetCell 调用这个方法。好的解决方案应该强制执行这一限制。</p>
<h5 id="纯虚方法和抽象基类">纯虚方法和抽象基类 </h5>
<p>纯虚方法（pure virtual methods）在类定义中显式说明该方法不需要定义。如果将某个方法设置为纯虚方法，就是告诉编译器当前类中不存在这个方法的定义。具有至少一个纯虚方法的类称为抽象类，因为这个类不能被实例化。编译器会强制接受这个事实：如果某个类包含一个或多个纯虚方法，就无法构建这种类型的对象。</p>
<p>采用专门的语法指定纯虚方法：方法声明后紧接着 =0.不需要编写任何代码。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">SpreadsheetCell</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">SpreadsheetCell</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                      <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span>        <span class="token function">set</span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span>string_view value<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-virtual">virtual</span> std<span class="token double-colon punctuation">::</span>string <span class="token function">getString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span>           <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>现在基类成了抽象类，无法创建 SpreadsheetCell 对象，下面的代码将无法编译。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>SpreadsheetCell cell<span class="token punctuation">;</span>
</code></pre><p>然而，一旦实现了 StringSpreadsheetCell 类，下面的代码就可成功编译，原因在于实例化了抽象基类的派生类。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>unique_ptr<span class="token operator">&lt;</span>SpreadsheetCell<span class="token operator">&gt;</span> <span class="token function">cell</span><span class="token punctuation">(</span><span class="token keyword keyword-new">new</span> StringSpreadsheetCell<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><blockquote>
<p><strong>注意：</strong></p>
<p>抽象类提供了一种禁止其他代码直接实例化对象的方法，而它的派生类可以实例化对象。</p>
</blockquote>
<p>注意：不需要实现 SpreadsheetCell 类。所有方法都是纯虚方法，析构函数显式地设置为默认。</p>
<h4 id="1044-独立的派生类">10.4.4 独立的派生类 </h4>
<p>编写 StingSpreadsheetCell 和 DoubleSpreadsheetCell 类只需要实现父类中定义的功能。由于想让客户实现并使用字符串单元格和双精度单元格，因此单元格不可以是抽象的——必须实现从父类继承的所有纯虚方法。如果派生类没有实现从父类继承的所有纯虚方法，那么派生类也是抽象的，客户就不能实例化派生类的对象。</p>
<h5 id="stringspreadsheetcell-类的定义">StringSpreadsheetCell 类的定义 </h5>
<p>编写 StringSpreadsheetCell 类定义的第一步是从 SpreadsheetCell 类继承。书中需要导入模块，咱就导入头文件。</p>
<p>第二部是重写继承的纯虚方法，此次不将其设置为 0。</p>
<p>最后一步是为字符串单元格添加一个私有数据成员 m_value，在其中存储实际单元格数据。这个数据成员是 std::optional（见第 1 章）。通过 optional 类型，可以确认是否没有设置单元格的值或单元格的值设为空字符串。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">StringSpreadsheetCell</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">SpreadsheetCell</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span>std<span class="token double-colon punctuation">::</span>string<span class="token operator">&gt;</span> m_value<span class="token punctuation">;</span>

<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-void">void</span> <span class="token function">set</span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span>string_view value<span class="token punctuation">)</span> <span class="token keyword keyword-override">override</span><span class="token punctuation">;</span>

    std<span class="token double-colon punctuation">::</span>string <span class="token function">getString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token keyword keyword-override">override</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><h5 id="stringspreadsheetcell-的实现">StringSpreadsheetCell 的实现 </h5>
<p>set() 方法十分简单，因为内部表示已经是一个字符串。getString() 方法必须考虑到 m_value 的类型是 optional，可能不具有值。如果 m_value 不具有值，getString() 将返回一个默认字符串，本例中是空字符串，可以使用 std::optional 的 value_or() 方法对此进行简化。使用 <code>m_value.value_or("")</code>，如果 m_value 包含实际的值，将返回相应的值，否则将返回空值。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-void">void</span>        <span class="token function">set</span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span>string_view value<span class="token punctuation">)</span> <span class="token keyword keyword-override">override</span> <span class="token punctuation">{</span> m_value <span class="token operator">=</span> value<span class="token punctuation">;</span> <span class="token punctuation">}</span>
std<span class="token double-colon punctuation">::</span>string <span class="token function">getString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token keyword keyword-override">override</span> <span class="token punctuation">{</span> <span class="token keyword keyword-return">return</span> m_value<span class="token punctuation">.</span><span class="token function">value_or</span><span class="token punctuation">(</span><span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
</code></pre><h5 id="doublespreadsheetcell-类的定义和实现">DoubleSpreadsheetCell 类的定义和实现 </h5>
<p>双精度版本遵循类似的模式，但具有不同的逻辑。除了基类的 set() 方法以 string_view 作为参数之外，还提供了新的 set() 方法以允许用户使用双精度设置其值。两个新的 private static 方法用于字符串和双精度的互相转换，反之亦然。与 StringSpreadsheetCell 相同，这个类也有一个 m_value 数据成员，此时这个成员的类型是 optional&lt;double&gt;。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">DoubleSpreadsheetCell</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">SpreadsheetCell</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span><span class="token keyword keyword-double">double</span><span class="token operator">&gt;</span> m_value<span class="token punctuation">;</span>

<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span> <span class="token function">set</span><span class="token punctuation">(</span><span class="token keyword keyword-double">double</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-void">void</span>         <span class="token function">set</span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span>string_view value<span class="token punctuation">)</span> <span class="token keyword keyword-override">override</span><span class="token punctuation">;</span>
    std<span class="token double-colon punctuation">::</span>string  <span class="token function">getString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token keyword keyword-override">override</span><span class="token punctuation">;</span>

<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    <span class="token keyword keyword-static">static</span> std<span class="token double-colon punctuation">::</span>string <span class="token function">doubleToString</span><span class="token punctuation">(</span><span class="token keyword keyword-double">double</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-static">static</span> <span class="token keyword keyword-double">double</span>      <span class="token function">stringToDouble</span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span>string_view value<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>以双精度作为参数的 set() 方法简单明了。string_view 版本使用了 private static 方法 stringToDouble()。getString() 方法返回存储的双精度作为字符串；如果未存储任何值，则返回一个空字符串。它使用 std::optional 的 has_value() 方法来查询 optional 是否具有实际值。如果具有值，则使用 value() 方法获取。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span> <span class="token function">set</span><span class="token punctuation">(</span><span class="token keyword keyword-double">double</span> value<span class="token punctuation">)</span> <span class="token punctuation">{</span> m_value <span class="token operator">=</span> value<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token keyword keyword-void">void</span>         <span class="token function">set</span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span>string_view value<span class="token punctuation">)</span> <span class="token keyword keyword-override">override</span> <span class="token punctuation">{</span> m_value <span class="token operator">=</span> <span class="token function">stringToDouble</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
std<span class="token double-colon punctuation">::</span>string  <span class="token function">getString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token keyword keyword-override">override</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> <span class="token punctuation">(</span>m_value<span class="token punctuation">.</span><span class="token function">has_value</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token function">doubleToString</span><span class="token punctuation">(</span>m_value<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>可以看到，在层次结构中实现电子表格单元格的主要优点是代码更加简单。每个对象都以自我为中心，只执行各自的功能。</p>
<h4 id="1045-利用多态性">10.4.5 利用多态性 </h4>
<p>现在 SpreadsheetCell 参差结构与具有多态性，客户代码可利用多态性提供的种种好处。下面的测试程序显式了这些功能。</p>
<p>为演示多态性，测试程序声明了一个具有 3 个 SpreadsheetCell 指针的矢量，记住由于 SpreadsheetCell 是一个抽象类，因此不能创建此种类型的对象。然而，仍然可以使用 SpreadsheetCell 的指针或引用，因为它实际上指向的是其中一个抽象类。由于是父类型 SpreadsheetCell 的矢量，因此可以任意存储两个派生类。这意味着矢量元素可以是 StringSpreadsheetCell 或 DoubleSpreadsheetCell。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>vector<span class="token operator">&lt;</span>unique_ptr<span class="token operator">&lt;</span>SpreadsheetCell<span class="token operator">&gt;&gt;</span> cellArray<span class="token punctuation">;</span>
</code></pre><p>矢量的前两个元素指向新建的 StringSpreadsheetCell，第三个元素指向一个新的 DoubleSpreadsheetCell。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>cellArray<span class="token punctuation">.</span><span class="token function">push_back</span><span class="token punctuation">(</span><span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span>StringSpreadsheetCell<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
cellArray<span class="token punctuation">.</span><span class="token function">push_back</span><span class="token punctuation">(</span><span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span>StringSpreadsheetCell<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
cellArray<span class="token punctuation">.</span><span class="token function">push_back</span><span class="token punctuation">(</span><span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span>DoubleSpreadsheetCell<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>现在矢量包含了多类型数据，基类声明的任何方法都可以应用到矢量中的对象。代码只是使用了 SpreadsheetCell 指针——编译器在编译期不知道对象的实际类型是什么。然而，由于这两个类是 SpreadsheetCell 的派生类，因此必须支持 SpreadsheetCell 的方法。</p>
<p>剩下的使用示例略了。</p>
<h4 id="1045-考虑将来">10.4.5 考虑将来 </h4>
<p>SpreadsheetCell 层次结构的新视线从面向对象设计的观点来看当然是一个进步。但是，对实际的电子表格程序来说，这个类层次结构还不够充分，主要有以下几个原因：</p>
<p>首先，即使不考虑改进设计，现在仍然缺少一个功能：将某个单元格类型转换为其他类型。由于将单元格分为两类，单元格对象的结合变得更松散。为提供将 DoubleSpreadsheetCell 转换为 StringSpreadsheetCell 的功能，应添加一个转换构造函数（或类型构造函数），这个构造函数类似于复制构造函数，但参数不是对同类对象的引用，而是对同级类对象的引用。另外注意，现在必须声明一个默认构造函数，可将其显式设置为默认，因为一旦自行声明仍和构造函数，编译器将停止生成。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">StringSpreadsheetCell</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">SpreadsheetCell</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">StringSpreadsheetCell</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token function">StringSpreadsheetCell</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> DoubleSpreadsheetCell<span class="token operator">&amp;</span> cell<span class="token punctuation">)</span>
        <span class="token operator">:</span> m_value<span class="token punctuation">{</span>cell<span class="token punctuation">.</span><span class="token function">getString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span>
    <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token comment">// 余下省略</span>
<span class="token punctuation">}</span>
</code></pre><p>通过转换构造函数，可以方便地用 DoubleSpreadsheetCell 创建 StringSpreadsheetCell。然而不要将其与指针或引用的类型混淆，<strong>类型转换无法将一个指针或引用转换为同级的另一个指针或引用</strong>，除非按照第 15 章讲述的方法重载类型转换运算符。</p>
<blockquote>
<p><strong>警告：</strong></p>
<p>在层次结构中，总可以向上转型，有时也可以向下转型。通过改变类型转换运算符的行为，或者使用 <em><strong>reinterpret_cast&lt;&gt;</strong></em>（不推荐采用这些方法），就可以在层次结构中进行类型转换。</p>
</blockquote>
<p>其次，如何为单元格实现运算符重载是一个很有趣的问题，在此有几种可能的解决方案，其中一种方案是：针对每个单元格组合，实现每个运算符的重载版本，由于只有两个派生类，因此这样做并不难。可编写一个 operator+ 函数，将两个双精度单元格相加，将两个字符串单元格相加，将双精度单元格与字符串单元格相加。另一种方案是给出一种通用表示，前面的实现已将字符串作为标准化的通用类型表示。通过这种通用表示，一个 operator+ 函数就可以处理所有情况。假定两个单元格相加的结果始终是字符串单元格，那么一个在 StringSpreadsheetCell 中的可能的实现如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>StringSpreadsheetCell <span class="token keyword keyword-operator">operator</span><span class="token operator">+</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> StringSpreadsheetCell<span class="token operator">&amp;</span> lhs<span class="token punctuation">,</span>   <span class="token comment">//</span>
                                <span class="token keyword keyword-const">const</span> StringSpreadsheetCell<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    StringSpreadsheetCell newCell<span class="token punctuation">;</span>
    newCell<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>lhs<span class="token punctuation">.</span><span class="token function">getString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> rhs<span class="token punctuation">.</span><span class="token function">getString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-return">return</span> newCell<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>只要编译器可将特定的单元格转换为 StringSpreadsheetCell，这个运算符就可以运行。考虑前面的示例，StringSpreadsheetCell 构造函数采用 DoubleSpreadsheetCell 作为参数，如果这是 operator+ 运行的唯一方法，那么编译器将自动执行转换。这意味着下面的代码可以运行，尽管 operator+ 被显式地用于 StringSpreadsheetCell。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>DoubleSpreadsheetCell myDbl<span class="token punctuation">;</span>
myDbl<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token number">8.4</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
StringSpreadsheetCell result<span class="token punctuation">{</span>myDbl <span class="token operator">+</span> myDbl<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>当然，相加的结果实际上并不是将数字相加，而是将双精度单元格转换为字符串单元格，然后将字符串相加。</p>
<h3 id="105-多重继承">10.5 多重继承 </h3>
<p>多重继承通常被认为是面向对象编程中一种复杂且不必要的部分。作者说自己判断这是否有用。</p>
<h4 id="1051-从多个类继承">10.5.1 从多个类继承 </h4>
<p>从语法角度看，定义具有多个父类的类很简单。为此，只需要在声明类名分别列出基类。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Baz</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Foo</span><span class="token punctuation">,</span> <span class="token keyword keyword-public">public</span> <span class="token class-name">Bar</span></span>
<span class="token punctuation">{</span> <span class="token comment">/* 省略 */</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>由于列出了多个父类，Baz 对象具有如下特性：</p>
<ul>
<li>Baz 对象支持 Foo 和 Bar 类的 public 方法，并且包含这两个类的数据成员。</li>
<li>Baz 类的方法有权访问 Foo 和 Bar 类的 protected 数据成员和方法。</li>
<li>Baz 对象可以向上转型为 Foo 或 Bar 对象。</li>
<li>创建新的 Baz 对象将自动调用 Foo 和 Bar 类的默认构造函数，并按照类定义中列出的类顺序进行。</li>
<li>删除 Baz 对象将自动调用 Foo 和 Bar 类的析构函数，调用顺序与类在类定义中顺序正好相反。</li>
</ul>
<div class="mermaid">classDiagram

class Dog
class Bird
class DogBird

Dog &lt;|-- DogBird
Bird &lt;|-- DogBird
</div><p>示例略。</p>
<p>使用就具有多个父类的类对象与使用具有单个父类的类对象没什么不同。实际上，客户代码甚至不需要知道这个类有两个父类。需要关心的只是这个类支持的属性和行为。在此情况下，子类支持父类的所有 public 方法。</p>
<h4 id="1052-名称冲突和歧义基类">10.5.2 名称冲突和歧义基类 </h4>
<p>多重继承崩溃的场景并不难想象，下面的示例显示了一些必须要考虑的边缘情况。</p>
<h5 id="名称歧义">名称歧义 </h5>
<p>如果 Dog 类和 Bird 类都有一个 eat() 方法，会发生什么？由于 Dog 类和 Bird 类毫不相干，eat() 方法的一个版本无法重写另一个版本——在派生类 DogBird 中这两个方法都存在。</p>
<p>只要客户代码不调用 eat() 方法，就不会出现问题。尽管有两个版本的 eat() 方法，但 DogBird 类仍然可以正确编译，然而，如果客户代码试图调用 DogBird 类的 eat() 方法，编译器将报错，指出对 eat() 方法的调用有歧义。编译器不知道该调用哪个版本。示例代码略。</p>
<p>为了消除歧义，可使用 dynamic_cast() 显式地将对象向上转型（本质上是向编译器隐藏多余的方法版本），也可以使用歧义消除语法。下面的代码显示了调用 eat() 方法的 Dog 版本的两种方案：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token generic-function"><span class="token function">dynamic_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span>Dog<span class="token operator">&amp;</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>myConfuseAnimal<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
myConfuseAnimal<span class="token punctuation">.</span><span class="token class-name">Dog</span><span class="token double-colon punctuation">::</span><span class="token function">eat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>使用与访问父类方法相同的语法（:: 运算符），派生类的方法本身可以显式地为同名的方法消除歧义。比如，DogBIrd 类可以定义自己的 eat() 方法，从而消除其他代码中的歧义错误。在方法内部，可以判断调用哪个父类版本：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">DogBird</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Dog</span><span class="token punctuation">,</span> <span class="token keyword keyword-public">public</span> <span class="token class-name">Bird</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-void">void</span> <span class="token function">eat</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-override">override</span> <span class="token punctuation">{</span> <span class="token class-name">Dog</span><span class="token double-colon punctuation">::</span><span class="token function">eat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>另一种防止歧义错误的方式是使用 using 语句显式指定，在 DogBird 类中应继承哪个版本的 eat() 方法，如下面的 DogBird 类定义所示。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">DogBird</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Dog</span><span class="token punctuation">,</span> <span class="token keyword keyword-public">public</span> <span class="token class-name">Bird</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-using">using</span> Dog<span class="token double-colon punctuation">::</span>eat<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><h5 id="歧义基类">歧义基类 </h5>
<p>另一种引起歧义的情况是从同一个类继承两次。例如，如果处于某种原因 Bird 类从 Dog 类继承，DogBird 类的代码将无法编译，因为 Dog 变成了歧义基类。</p>
<p>示例略。</p>
<p>多数歧义基类的情况或者是人为的 “what-if” 示例引起的，或者是由于类层次结构的混乱引起的。下图显示了前面示例中的类图（没有哈），并指出了歧义。</p>
<div class="mermaid">classDiagram

class Dog
class Bird
class DogBird

Dog &lt;|-- Bird
Bird &lt;|-- DogBird
Dog &lt;|-- DogBird
</div><p>数据成员也可以引起歧义。如果 Dog 和 Bird 类具有同名的数据成员，当客户代码试图访问这个成员时，就会发生歧义错误。</p>
<p>多个父类本身也可能有共同的父类。例如，Bird 和 Dog 类可能都是 Animal 类的派生类，如下图所示。</p>
<div class="mermaid">classDiagram

class Animal
class Dog
class Bird
class DogBird

Animal &lt;|-- Dog
Animal &lt;|-- Bird
Dog &lt;|-- DogBird
Bird &lt;|-- DogBird
</div><p>C++ 允许这种类型的类层次结构，尽管这样存在着名称歧义。例如，如果 Animal 类还有一个公有方法 sleep()，DogBird 对象无法调用这个方法，因为编译器不知道该调用 Dog 类继承的版本还是 Bird 类继承的版本。</p>
<p>使用“菱形”类层次结构的最佳方法是<strong>将最顶部的类设置为抽象类，将所有方法都设置为纯虚方法</strong>。由于类只声明方法而不提供定义，在基类中没有方法可以调用，因此在这个层次上就没有歧义。</p>
<p>下例实现了菱形类层次结构，其中有一个每个派生类都必须定义的纯虚方法 eat()。DogBird 类仍然必须显式说明使用哪个父类的 eat() 方法，但是 Dog 和 Bird 类引起歧义的原因是它们具有相同的方法，而不是因为从同一个类继承。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;iostream&gt;</span></span>

<span class="token keyword keyword-using">using</span> <span class="token keyword keyword-namespace">namespace</span> std<span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Animal</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
        <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span> <span class="token function">eat</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Dog</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Animal</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
<span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span> <span class="token function">bark</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Woof!"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token keyword keyword-void">void</span>         <span class="token function">eat</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-override">override</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"The dog ate."</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Bird</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Animal</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
<span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span> <span class="token function">bark</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Chirp!"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token keyword keyword-void">void</span>         <span class="token function">eat</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-override">override</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"The bird ate."</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">DogBird</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Dog</span><span class="token punctuation">,</span> <span class="token keyword keyword-public">public</span> <span class="token class-name">Bird</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
<span class="token keyword keyword-using">using</span> Dog<span class="token double-colon punctuation">::</span>eat<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>虚基类是处理菱形类层次结构中顶部类的更好方法，将在本章最后讲述。</p>
<h5 id="多重继承的用途">多重继承的用途 </h5>
<p>为什么程序员要在代码中使用多重继承？多重继承最直接的用例就是定义一个既“是一个”事物，又“是一个”其他事物的类对象。第 5 章已经说过，遵循这个模式的实际对象很难恰当地转换为代码。</p>
<p>多重继承最简单有力的用途就是实现混入（mix-in）类。混入类的介绍在第 5 章，更多详细在第 32 章“整合设计技术和框架”。</p>
<p>使用多重继承的另一个原因是给基于组件的类建模。第 5 章给出了飞机模拟示例，Airplane 类有引擎、机身、控制系统和其他组件。尽管 Airplane 类的典型实现是将这些组件当作独立的数据成员，但也可以使用多重继承。飞机类可从引擎、机身、控制系统继承，从而有效地获得这些组件的行为和属性。建议不要使用这种类型的代码，这将“有一个”关系与继承混淆了，而继承用于“是一个”关系。推荐的解决方案是让 Airplane 类包含 Engine、Fuselage 和 Controls 类型的数据成员。</p>
<h3 id="106-有趣而晦涩的继承问题">10.6 有趣而晦涩的继承问题 </h3>
<p>扩展类引发了多种问题。类的哪些特征可以改变。类的哪些特征可以改变，哪些不能改变？什么是非公共继承？什么是虚基类？下面将回答这些问题。</p>
<h4 id="1061-修改重写方法的返回类型">10.6.1 修改重写方法的返回类型 </h4>
<p>重写某个方法的主要原因是为了修改方法的实现。然而，有时是为了修改方法的其他特征，比如返回类型。</p>
<p>根据经验，重写方法要使用与基类一致的方法声明（或方法原型）。实现可以改变，但原型保持不变。</p>
<p>然而事实未必总是如此，在 C++ 中，如果原始的返回类型是某个类的指针或引用，重写的方法可将返回类型改为派生类的指针或引用。这种类型称为协变返回类型（covariant return types）。如果基类和派生类处于平行层次结构（parallel hierarchy）中，使用这个特性可以带来便利。平行层次结构是指，一个类层次结构与另一个类层次结构没有相交，但是存在联系。</p>
<p>例如，考虑樱桃果园的模拟程序。可使用两个类层次结构模拟不同但明显相关的实际对象。第一个是 Cherry 类层次结构，Cherry 基类有一个 BingCherry 的派生类。与此类似，另一个类层次结构的基类为 CherryTree，派生类为 BingCherryTree。下图显示了这两个类层次结构。</p>
<div class="mermaid">classDiagram

class Cherry
class BingCherry
class CherryTree
class BingCherryTree

Cherry &lt;|-- BingCherry
CherryTree &lt;|-- BingCherryTree
</div><p>现在假定 CherryTree 类有一个虚方法 pick()，这个虚方法从樱桃树上获取一个樱桃：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Cherry<span class="token operator">*</span> <span class="token class-name">CherryTree</span><span class="token double-colon punctuation">::</span><span class="token function">pick</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> <span class="token keyword keyword-new">new</span> Cherry<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><blockquote>
<p><strong>注意：</strong></p>
<p>为了演示如何更改返回类型，本例未返回智能指针，而是返回普通指针。本节结尾将解释其中的原因。当然，调用者应当在智能指针（而非普通指针）中立即存储结果。</p>
</blockquote>
<p>在 BingCherryTree 派生类中，要重写这个方法。或许 BingCherry 在摘下来时需要擦拭（作者说请允许我们这么说）。由于 BingCherry 也是Cherry，在下例中，方法的原型保持不变，而方法却被重写。BingCherry 指针被自动转换为 Cherry 指针。注意，这个实现使用 unique_ptr 确保当 polish() 抛出异常时，没有泄漏内存。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Cherry<span class="token operator">*</span> <span class="token class-name">BingCherryTree</span><span class="token double-colon punctuation">::</span><span class="token function">pick</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-auto">auto</span> theCherry<span class="token punctuation">{</span><span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span>BingCherry<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    theCherry<span class="token operator">-&gt;</span><span class="token function">polish</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-return">return</span> theCherry<span class="token punctuation">.</span><span class="token function">release</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>上面的实现非常好。然而，由于 BingCherryTree 类始终返回 BIngCherry 对象，因此可通过修改返回类型，向这个类的潜在用户指明这一点，如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>BingCherry<span class="token operator">*</span><span class="token class-name">BingCherryTree</span><span class="token double-colon punctuation">::</span><span class="token function">pick</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-auto">auto</span> theCherry<span class="token punctuation">{</span><span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span>BingCherry<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    theCherry<span class="token operator">-&gt;</span><span class="token function">polish</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-return">return</span> theCherry<span class="token punctuation">.</span><span class="token function">release</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>下面的示例显式了 BingCherryTree:;pick() 方法的用法。printType() 是基类 Cherry 的虚方法，有 BingCherryTree 派生类的重写，用于输出 cherry 的类型。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>BingCherryTree     theTree<span class="token punctuation">;</span>
unique_ptr<span class="token operator">&lt;</span>Cherry<span class="token operator">&gt;</span> theCherry<span class="token punctuation">{</span>theTree<span class="token punctuation">.</span><span class="token function">pick</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
theCherry<span class="token operator">-&gt;</span><span class="token function">printType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>为判断能否修改重写方法的返回类型，可以考虑已有代码是否能够继续运行，这里称为里氏替换原则（LSP）。在上例中，修改返回类型没有问题，因为假定 pick() 方法总是返回 Cherry* 的代码仍然可以成功编译并正常运行。由于 BingCherry 也是 Cherry，因此根据 CherryTree 版本的 pick() 返回值调用的任何方法，仍然可以基于 BingCherryTree 版本的 pick() 返回值进行调用。</p>
<p>不能将返回类型修改为完全不相关的类型，例如 void*。下面的代码无法编译：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-void">void</span><span class="token operator">*</span> <span class="token class-name">BingCherryTree</span><span class="token double-colon punctuation">::</span><span class="token function">pick</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span><span class="token punctuation">}</span>   <span class="token comment">// 编译不通过</span>
</code></pre><p>这段代码会导致编译错误。</p>
<p>如前所述，这个示例正用普通指针替代智能指针。将 unique_ptr 用作返回类型时，这不能用于本例。假设 CherryTree::pick() 方法返回 unique_ptr&lt;Cherry&gt;，如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>unique_ptr<span class="token operator">&lt;</span>Cherry<span class="token operator">&gt;</span> <span class="token class-name">CherryTree</span><span class="token double-colon punctuation">::</span><span class="token function">pick</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> <span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span>Cherry<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>此时，无法将 BingCherry::pick() 方法的返回类型改成 unique_ptr&lt;BingCherry&gt;。下面的代码无法编译：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>unique_ptr<span class="token operator">&lt;</span>BingCherry<span class="token operator">&gt;</span> <span class="token class-name">CherryTree</span><span class="token double-colon punctuation">::</span><span class="token function">pick</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-override">override</span>   <span class="token comment">// 编译不通过</span>
<span class="token punctuation">{</span>
    <span class="token comment">/* ... */</span>
<span class="token punctuation">}</span>
</code></pre><p>愿意在于 unique_ptr 是类模板。创建了 unique_ptr 类模板的两个实例 unique_ptr&lt;Cherry&gt; 和 unique_ptr&lt;BingCherry&gt;。这两个实例是完全不同的类型，完全无关，无法更改重写方法的返回类型来返回完全不同的类型。</p>
<h4 id="1062-派生类中添加虚基类方法的重载">10.6.2 派生类中添加虚基类方法的重载 </h4>
<p>可以向派生类中添加虚基类方法的新重载。也就是说，可以使用新原型在派生类中添加虚方法的重载，但继续继承基类版本。该技术使用 using 声明将方法的基类定义显式包含在派生类中。实例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Base</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span> <span class="token function">someMethod</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Derived</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Base</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-using">using</span> Base<span class="token double-colon punctuation">::</span>someMethod<span class="token punctuation">;</span>           <span class="token comment">// 显式地继承基类的版本</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span> <span class="token function">someMethod</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment">// 添加一个新的继承方法</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span> <span class="token function">someOtherMethod</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><blockquote>
<p><strong>注意：</strong></p>
<p>对于派生类和基类同名的方法，具有不同参数列表的情况很少见。</p>
</blockquote>
<h4 id="1063-继承的构造函数">10.6.3 继承的构造函数 </h4>
<p>上一节提到，可以在派生类中使用 using 关键字显式地包含基类中定义的方法。这适用于普通类方法，也适用于构造函数。考虑以下 Base 和 derived 类定义：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;string&gt;</span></span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Base</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">Base</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token function">Base</span><span class="token punctuation">(</span><span class="token punctuation">)</span>          <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token function">Base</span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span>string_view str<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Derived</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Base</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">Derived</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>只能用提供的 Base 构造函数构建 Base 对象，要么是默认构造函数，要么是包含 string_view 参数的构造函数。另外，只能用 Derived 构造函数创建 Derived 对象，这个构造函数需要一个整数作为参数。不能使用 Base 类中接收 string_view 的构造函数构建 Derived 对象，例如：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Base    base<span class="token punctuation">{</span><span class="token string">"Hello"</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
Derived derived1<span class="token punctuation">{</span><span class="token number">1</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
Derived derived2<span class="token punctuation">{</span><span class="token string">"Hello"</span><span class="token punctuation">}</span><span class="token punctuation">;</span>   <span class="token comment">// 编译不通过</span>
Derived derived2<span class="token punctuation">;</span>            <span class="token comment">// 编译不通过</span>
</code></pre><p>如果喜欢使用基于 string_view 的 Base 构造函数构建 Derived 对象，可在 Derived 类中显式地继承 Base 构造函数，如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Derived</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Base</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-using">using</span> Base<span class="token double-colon punctuation">::</span>Base<span class="token punctuation">;</span>
    <span class="token function">Derived</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p><strong>using 语句从父类继承除默认构造函数外的其他所有构造函数</strong>，现在可通过下面两种方法构建 Derived 对象。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Derived derived1<span class="token punctuation">{</span><span class="token number">1</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
Derived derived2<span class="token punctuation">{</span><span class="token string">"Hello"</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
Derived derived2<span class="token punctuation">;</span>
</code></pre><p>当然，Derived 类可以从所有 Base 类中继承构造函数，而不定义任何构造函数。实例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Base</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">Base</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token function">Base</span><span class="token punctuation">(</span><span class="token punctuation">)</span>          <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token function">Base</span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span>string_view str<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">Base</span><span class="token punctuation">(</span><span class="token keyword keyword-float">float</span> f<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Derived</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Base</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-using">using</span> Base<span class="token double-colon punctuation">::</span>Base<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>可以如下创建 Derived 类的实例：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Derived derived1<span class="token punctuation">{</span><span class="token string">"Hello"</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
Derived derived2<span class="token punctuation">{</span><span class="token number">1.23f</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
Derived derived3<span class="token punctuation">;</span>
</code></pre><h5 id="隐藏继承的构造函数">隐藏继承的构造函数 </h5>
<p>定义的构造函数可与从 Base 类继承的构造函数有相同的参数列表。与所有的重写一样，此时 Derived 类的构造函数的优先级高于继承的构造函数。在下例中，Derived 类使用 using 关键字，继承了 Base 类中除默认构造函数外的其他所有构造函数。然而，由于 Derived 类定义了一个使用浮点数作为参数的构造函数，从 Base 类继承的使用浮点数作为参数的构造函数被重写。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Base</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">Base</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token function">Base</span><span class="token punctuation">(</span><span class="token punctuation">)</span>          <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token function">Base</span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span>string_view str<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">Base</span><span class="token punctuation">(</span><span class="token keyword keyword-float">float</span> f<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Derived</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Base</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-using">using</span> Base<span class="token double-colon punctuation">::</span>Base<span class="token punctuation">;</span>
    <span class="token function">Derived</span><span class="token punctuation">(</span><span class="token keyword keyword-float">float</span> f<span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment">// 隐藏了基类的方法</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>根据这个定义，可用下面的代码创建 Derived 对象：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Derived derived1<span class="token punctuation">{</span><span class="token string">"Hello"</span><span class="token punctuation">}</span><span class="token punctuation">;</span>   <span class="token comment">// 通过，调用基类方法</span>
Derived derived2<span class="token punctuation">{</span><span class="token number">1.23f</span><span class="token punctuation">}</span><span class="token punctuation">;</span>     <span class="token comment">// 通过，调用 Derived 的方法</span>
Derived derived3<span class="token punctuation">;</span>            <span class="token comment">// 通过，调用基类方法</span>
</code></pre><p>使用 using 子句从基类继承构造函数有一些限制。</p>
<ul>
<li>当从基类继承构造函数时，会继承除默认构造函数外的其他全部构造函数，不能只是继承基类构造函数的一个子集。</li>
<li>当继承构造函数时，无论 using 声明位于哪个访问规范下，都将使用与基类中相同的访问规范来继承。</li>
</ul>
<h5 id="继承的构造函数和多重继承">继承的构造函数和多重继承 </h5>
<p>第二个限制与多重继承有关。如果一个基类的某个构造函数与另一个基类的构造函数具有相同的参数列表，就不可能从基类继承构造函数，因为那样会导致歧义。为解决这个问题，Derived 类必须显式地定义冲突的构造函数。例如，下面的 Derived 类试图继承 Base1 和 Base2 基类的所有构造函数，这鬼导致编译错误，因为使用浮点是作为参数的构造函数存在歧义。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Base1</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">Base1</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token function">Base1</span><span class="token punctuation">(</span><span class="token punctuation">)</span>          <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token function">Base1</span><span class="token punctuation">(</span><span class="token keyword keyword-float">float</span> f<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Base2</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">Base2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token function">Base2</span><span class="token punctuation">(</span><span class="token punctuation">)</span>          <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token function">Base2</span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span>string_view str<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">Base2</span><span class="token punctuation">(</span><span class="token keyword keyword-float">float</span> f<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Derived</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Base1</span><span class="token punctuation">,</span> <span class="token keyword keyword-public">public</span> <span class="token class-name">Base2</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-using">using</span> Base1<span class="token double-colon punctuation">::</span>Base1<span class="token punctuation">;</span>
    <span class="token keyword keyword-using">using</span> Base2<span class="token double-colon punctuation">::</span>Base2<span class="token punctuation">;</span>
    <span class="token function">Derived</span><span class="token punctuation">(</span><span class="token keyword keyword-char">char</span> c<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-int">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    Derived d<span class="token punctuation">{</span><span class="token number">1.2f</span><span class="token punctuation">}</span><span class="token punctuation">;</span>   <span class="token comment">// 错误</span>
<span class="token punctuation">}</span>
</code></pre><p>Derived 类定义中的第一条 using 语句继承了 Base1 类的构造函数。这意味着 Derived 类具有如下构造函数：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Derived<span class="token punctuation">{</span><span class="token keyword keyword-float">float</span> f<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>Derived 类定义中的第二条 using 子句试图继承 Base2 类的全部构造函数。然而，这会导致编译错误，因为这意味着 Derived 类拥有第二个 Derived(float f) 构造函数。为解决这个问题，可在 Derived 类中<strong>显式声明冲突的构造函数</strong>，如下所示。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Derived</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Base1</span><span class="token punctuation">,</span> <span class="token keyword keyword-public">public</span> <span class="token class-name">Base2</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-using">using</span> Base1<span class="token double-colon punctuation">::</span>Base1<span class="token punctuation">;</span>
    <span class="token keyword keyword-using">using</span> Base2<span class="token double-colon punctuation">::</span>Base2<span class="token punctuation">;</span>
    <span class="token function">Derived</span><span class="token punctuation">(</span><span class="token keyword keyword-char">char</span> c<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">Derived</span><span class="token punctuation">(</span><span class="token keyword keyword-float">float</span> f<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>现在，Derived 类显式地声明了一个采用浮点数作为参数的构造函数。从而解决了歧义问题。如果愿意，在 Derived 类中显式声明的使用浮点数作为参数的构造函数仍然可以在 ctor-initializer 中调用 Base1 和 Base2 构造函数，如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token class-name">Derived</span><span class="token double-colon punctuation">::</span><span class="token function">Derived</span><span class="token punctuation">(</span><span class="token keyword keyword-float">float</span> f<span class="token punctuation">)</span>
    <span class="token operator">:</span> Base1<span class="token punctuation">{</span>f<span class="token punctuation">}</span>
    <span class="token punctuation">,</span> Base2<span class="token punctuation">{</span>f<span class="token punctuation">}</span>
<span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre><h5 id="数据成员的初始化">数据成员的初始化 </h5>
<p>当使用继承的构造函数时，要确保所有成员变量都正确地初始化。比如，考虑下面 Base 和 Derived 类的新定义。这个示例没有正确地初始化 m_int 数据成员，在任何情况下这都是一个严重错误。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Base</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    std<span class="token double-colon punctuation">::</span>string m_str<span class="token punctuation">;</span>

<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">Base</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token function">Base</span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span>string_view str<span class="token punctuation">)</span>
        <span class="token operator">:</span> m_str<span class="token punctuation">{</span>str<span class="token punctuation">}</span>
    <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Derived</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Base</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    <span class="token keyword keyword-int">int</span> m_int<span class="token punctuation">;</span>

<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-using">using</span> Base<span class="token double-colon punctuation">::</span>Base<span class="token punctuation">;</span>
    <span class="token function">Derived</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> i<span class="token punctuation">)</span>
        <span class="token operator">:</span> Base<span class="token punctuation">{</span><span class="token string">""</span><span class="token punctuation">}</span>
        <span class="token punctuation">,</span> m_int<span class="token punctuation">{</span>i<span class="token punctuation">}</span>
    <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>可采用如下方法创建一个 Derived 对象：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Derived s1<span class="token punctuation">{</span><span class="token number">2</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>这条语句将调用 Derived(int i) 构造函数，这个构造函数将初始化 Derived 类的 m_int 数据成员，并调用 Base 构造函数，用空字符串初始化 m_str 数据成员。</p>
<p>由于 Derived 类继承了 Base 构造函数，还可按下面的方式创建一个 Derived 对象：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Derived s2<span class="token punctuation">{</span><span class="token string">"Hello World"</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>这条语句调用从 Base 类继承的构造函数。然而，从 Base 类继承的构造函数只初始化了 Base 类的 m_str 成员变量，没有初始化 Derived 类的 m_int 成员变量，m_int 处于未初始化状态。通常不建议这么做。</p>
<p>解决方法时使用类内成员初始化器，第 8 章已讨论此特性。以下代码使用类内成员初始化器将 m_int初始化为 0。Derived(int i) 构造函数仍可修改这一初始化行为，将 m_int初始化为 参数 i 的值。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Derived</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Base</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    <span class="token keyword keyword-int">int</span> m_int<span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-using">using</span> Base<span class="token double-colon punctuation">::</span>Base<span class="token punctuation">;</span>
    <span class="token function">Derived</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> i<span class="token punctuation">)</span>
        <span class="token operator">:</span> Base<span class="token punctuation">{</span><span class="token string">""</span><span class="token punctuation">}</span>
        <span class="token punctuation">,</span> m_int<span class="token punctuation">{</span>i<span class="token punctuation">}</span>
    <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><h4 id="1064-重写时的特殊情况">10.6.4 重写时的特殊情况 </h4>
<p>当重写方法时，需要注意几种特殊情况。本节将列出可能遇到的一些情况。</p>
<h5 id="静态基类方法">静态基类方法 </h5>
<p>在 C++ 中，不能重写静态方法。对于多数情况而言，知道这一点就足够了。然而，在此需要了解一些推论。</p>
<p>首先，方法不可能既是静态的又是虚的。出于这个原因，试图重写一个静态方法并不能得到预期的结果。如果派生类中存在的静态方法与基类中的静态方法同名，实际上这是两个独立的方法。</p>
<p>下面的代码显示了两个类，这两个类都有一个名为 beStatic() 的静态方法。这两个方法毫无关系。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">BaseStatic</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-static">static</span> <span class="token keyword keyword-void">void</span> <span class="token function">beStatic</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"BaseStatic being static."</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">DerivedStatic</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">BaseStatic</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-static">static</span> <span class="token keyword keyword-void">void</span> <span class="token function">beStatic</span><span class="token punctuation">(</span><span class="token punctuation">)</span>   <span class="token comment">//</span>
    <span class="token punctuation">{</span>
        cout <span class="token operator">&lt;&lt;</span> <span class="token string">"DerivedStatic keepin' it static."</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>由于静态方法属于类，因此调用两个类的同名方法时，将调用各自的方法。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token class-name">BaseStatic</span><span class="token double-colon punctuation">::</span><span class="token function">beStatic</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">DerivedStatic</span><span class="token double-colon punctuation">::</span><span class="token function">beStatic</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>输出略，调用各自方法。</p>
<p>用类名访问这些方法时一切都很正常。当设计对象时，这一行为就不是那么明显。在 C++ 中，可以使用对象调用静态方法，但由于方法是静态的，因此没有 this 指针，也无法访问对象本身，使用对象调用静态方法，等价于使用 classname::method() 调用静态方法。回到前面的示例，可以编写如下代码，但结构令人惊讶。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>DerivedStatic myDerivedStatic<span class="token punctuation">;</span>
BaseStatic<span class="token operator">&amp;</span>   ref<span class="token punctuation">{</span>myDerivedStatic<span class="token punctuation">}</span><span class="token punctuation">;</span>
myDerivedStatic<span class="token punctuation">.</span><span class="token function">beStatic</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
ref<span class="token punctuation">.</span><span class="token function">beStatic</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>对 beStatic() 的第一次调用显然调用了 DerivedStatic 版本，因为调用它的对象被显式地声明为 DerivedStatic 对象。第二次调用的运行方式可能并非预期的那样。这个对象是一个 BaseStatic 引用，但指向的是一个 DerivedStatic 对象。在此情况下，会调用 BaseStatic 版本的 beStatic()。原因是当调用静态方法时，C++ 不关心对象实际上是什么，只关心编译期的类型。在此情况下，该类型为指向 BaseStatic 对象的引用。</p>
<p>输出结果略。</p>
<blockquote>
<p><strong>注意：</strong></p>
<p>静态方法属于定义它的类，而不属于特定的对象。当类中的方法调用静态方法时，所调用的版本是通过正常的名称解析来决定的。当使用对象调用时，对象实际上并不涉及调用，只是用来判断编译期的类型。</p>
</blockquote>
<h5 id="重载基类方法">重载基类方法 </h5>
<p>当指定名称和一组参数以重写某个方法时，编译器隐式地隐藏基类中同名的所有其他实例。想法为：如果重写类给定名称的某个方法，可能是想重写所有的同名方法，只是忘记这么做了，因此应该作为错误处理。这是有意义的，可以这么考虑——为什么要修改方法的某些版本而不修改其他版本呢？考虑下面的 Derived 类，它重写了一个方法，而没有重写相关的同级重载方法。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Base</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">Base</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span> <span class="token function">overload</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Base's overload()"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span> <span class="token function">overload</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> i<span class="token punctuation">)</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Base's overload(int i)"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Derived</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Base</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-void">void</span> <span class="token function">overload</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-override">override</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Derived's overload()"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>如果试图用 Derived 对象调用以 int 值作为参数的 overload() 版本，代码将无法编译，因为没有显式地重写这个方法。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Derived myDerived<span class="token punctuation">;</span>
myDerived<span class="token punctuation">.</span><span class="token function">overload</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment">// 编译不通过</span>
</code></pre><p>然而，使用 Derived 对象访问该版本的方法是可行的。只需要使用指向 Base 对象的指针或引用。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Derived myDerived<span class="token punctuation">;</span>
Base<span class="token operator">&amp;</span>   ref<span class="token punctuation">{</span>myDerived<span class="token punctuation">}</span><span class="token punctuation">;</span>
ref<span class="token punctuation">.</span><span class="token function">overload</span><span class="token punctuation">(</span><span class="token number">7</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>在 C++ 中，隐藏未实现的重载方法只是表象。显式声明为子类型实例的对象无法使用这些方法，但可将其转换为积累类型，以使用这些方法。</p>
<p>如果指向改变一个方法，可以使用 using 关键字避免重载该方法的所有版本。在下面的代码中，Derived 类定义中使用了从 Base 类继承的一个 overload() 版本，并显式地重写了另一个版本。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Derived</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Base</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-using">using</span> Base<span class="token double-colon punctuation">::</span>overload<span class="token punctuation">;</span>
    <span class="token keyword keyword-void">void</span> <span class="token function">overload</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-override">override</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Derived's overload()"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>using 子句存在一定风险。假定在 Base 类中添加了第三个 overload()，本来应该在 Derived 类中重写这个方法。但由于使用了 using 子句，在派生类中没有重写这个方法不会被当作错误，Derived 类显式地说明“接收父类其他所有的重载方法”。</p>
<blockquote>
<p><strong>警告：</strong></p>
<p>为了避免歧义 bug，应该重写所有版本的重载方法。</p>
</blockquote>
<h5 id="基类的-private-方法">基类的 private 方法 </h5>
<p>重写 private 方法当然没有问题。记住方法的访问说明符会判断谁可以调用这些方法。派生类无法调用父类的 private 方法，并不意味着无法重写这个方法。实际上，在 C++ 中，重写 private 方法是一种常见模式。这种模式允许派生类定义自身的“独特性”，在基类中会引用这种独特性，注意 Java 和 C# 允许重写 public 和 protected 方法，但不能重写 private 方法。</p>
<p>例如，下面的类是汽车模拟程序的一部分，根据汽油消耗量和剩余的燃料计算汽车可以形式的里程。getMilesLeft() 是所谓的模板方法。通常，模板方法都不是虚方法。模板方法通过在基类中定义一些算法的骨架，并调用虚方法来完成相关信息的查询。在子类中，可以重写这些虚方法，并修改相关算法而不会真正修改基类中已定义的算法。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">MileEstimator</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">MileEstimator</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-int">int</span> <span class="token function">getMilesLeft</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span>   <span class="token comment">//</span>
    <span class="token punctuation">{</span>
        <span class="token keyword keyword-return">return</span> <span class="token function">getMilePerGallon</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token function">getMilesLeft</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span> <span class="token function">setGallonsLeft</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> gallons<span class="token punctuation">)</span>   <span class="token comment">//</span>
    <span class="token punctuation">{</span>
        m_gallonsLeft <span class="token operator">=</span> gallons<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-int">int</span> <span class="token function">getGallonsLeft</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token punctuation">{</span> <span class="token keyword keyword-return">return</span> m_gallonsLeft<span class="token punctuation">;</span> <span class="token punctuation">}</span>

<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    <span class="token keyword keyword-int">int</span>         m_gallonsLeft<span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-int">int</span> <span class="token function">getMilePerGallon</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token punctuation">{</span> <span class="token keyword keyword-return">return</span> <span class="token number">20</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>getMilesLeft() 方法根据两个方法的返回结果执行计算。下面的代码使用 MilesEstimator 计算两加仑汽油可以行驶的里程：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>MileEstimator myMilesEstimator<span class="token punctuation">;</span>
myMilesEstimator<span class="token punctuation">.</span><span class="token function">setGallonsLeft</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
cout <span class="token operator">&lt;&lt;</span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"Normal estimator can go {} more miles."</span><span class="token punctuation">,</span>   <span class="token comment">//</span>
               myMilesEstimator<span class="token punctuation">.</span><span class="token function">getMilesLeft</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
     <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
</code></pre><p>代码输出结果略。</p>
<p>为让这个模拟程序更加有趣，可引入不同类型的车辆，或许是效率更高的汽车。现有的 MIleEstimator 假定所有的汽车燃烧一加仑的汽油可以跑 20 公里，这个值是从一个单独的方法返回的，因此派生类可以重写这个方法。下面就是这样一个派生类：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">EfficientCarMileEstimator</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">MileEstimator</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    <span class="token keyword keyword-int">int</span> <span class="token function">getMilesPerGallon</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token keyword keyword-override">override</span> <span class="token punctuation">{</span> <span class="token keyword keyword-return">return</span> <span class="token number">35</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>通过重写这个 private 方法，新类完全修改了没有更改的现有 public 方法的行为。基类中的 getMileLeft() 方法将自动调用private getMilesPerGallon() 方法的重写版本。下面是一个使用新类的示例：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>EfficientCarMileEstimator myEstimator<span class="token punctuation">;</span>
myEstimator<span class="token punctuation">.</span><span class="token function">setGallonsLeft</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
cout <span class="token operator">&lt;&lt;</span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"Efficient estimator can go {} more miles."</span><span class="token punctuation">,</span>   <span class="token comment">//</span>
               myEstimator<span class="token punctuation">.</span><span class="token function">getMilesLeft</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
     <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
</code></pre><p>输出结果略。</p>
<blockquote>
<p><strong>注意：</strong></p>
<p>重写 private 或 protected 方法可在不做重大改动的情况下改变类的某些特性。</p>
</blockquote>
<h5 id="基类方法具有默认参数">基类方法具有默认参数 </h5>
<p>派生类与基类可以具有不同的默认参数，但使用的参数取决于声明的变量类型，而不是底层的对象。下面是一个简单的派生类示例，派生类在重写的方法中提供了不同的默认参数。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Base</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">Base</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span> <span class="token function">go</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> i <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Base's go with i="</span> <span class="token operator">&lt;&lt;</span> i <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Derived</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Base</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-void">void</span> <span class="token function">go</span><span class="token punctuation">(</span><span class="token keyword keyword-int">int</span> i <span class="token operator">=</span> <span class="token number">7</span><span class="token punctuation">)</span> <span class="token keyword keyword-override">override</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Derived' go with i="</span> <span class="token operator">&lt;&lt;</span> i <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>如果调用 Derived 对象的 go()，将执行 Derived 版本的 go()，默认参数为 7。如果调用 Base 对象的 go()，将执行 Base 版本的 go()，默认参数为 2.然而（有些怪异），如果使用实际指向 Derived 对象的 Base 指针或 Base 引用调用 go()，将调用 Derived 版本的 go()，但实际使用 Base 版本的默认参数 2。下面的示例显示了这种行为：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Base    myBase<span class="token punctuation">;</span>
Derived myDerived<span class="token punctuation">;</span>
Base<span class="token operator">&amp;</span>   myBaseReferenceToDerived<span class="token punctuation">{</span>myDerived<span class="token punctuation">}</span><span class="token punctuation">;</span>

myBase<span class="token punctuation">.</span><span class="token function">go</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
myDerived<span class="token punctuation">.</span><span class="token function">go</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
myBaseReferenceToDerived<span class="token punctuation">.</span><span class="token function">go</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>代码的输出结果略。</p>
<p>产生这种行为的原因是 C++ 根据表达式的编译期类型（而非运行期类型）绑定默认参数。在 C++ 中，<strong>默认参数不会被“继承”</strong>。如果上面的 Derived 类没有像父类那样提供默认参数，就用新的非零参数版本重载 go() 方法。</p>
<blockquote>
<p><strong>注意：</strong></p>
<p><strong>当重写具有默认参数的方法时，也应该提供默认参数，这个参数的值应该与基类版本相同</strong>。建议使用命名的常量作为默认值，这样可在派生类中使用同一个命名的常量。</p>
</blockquote>
<h5 id="基类方法具有不同的访问级别">基类方法具有不同的访问级别 </h5>
<p>可以采用两种方法修改方法的访问级别——可以加强限制，也可放宽限制。在 C++ 中，这两种方法的意义都不大，但是这么做也是有合理原因的。</p>
<p>为加强某个方法（或数据成员）的限制，有两种方法。一种方法是修改整个基类的访问说明符，本章后面将讲述这种方法。另一种是在派生类种重新定义访问限制，下面的 Shy 类演示了这种方法：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Gregarious</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span> <span class="token function">talk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"gregarious says hi!"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Shy</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Gregarious</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-protected">protected</span><span class="token operator">:</span>
    <span class="token keyword keyword-void">void</span> <span class="token function">talk</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-override">override</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Shy reluctantly says hello."</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>Shy 类种 protected 版本的 talk() 方法适当地重写 Gregarious::talk() 方法。任何客户代码试图使用 Shy 对象调用 talk() 都会导致编译错误。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Shy myShy<span class="token punctuation">;</span>
myShy<span class="token punctuation">.</span><span class="token function">talk</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment">// 编译错误，不可访问</span>
</code></pre><p>然而，这个方法并不是完全受保护的。可使用 Gregarious 引用或指针访问 protected 方法。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Shy        myShy<span class="token punctuation">;</span>
Gregarious ref<span class="token punctuation">{</span>myShy<span class="token punctuation">}</span><span class="token punctuation">;</span>
ref<span class="token punctuation">.</span><span class="token function">talk</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>输出略。</p>
<p>这说明在派生类中将方法设置为 protected 实际上是重写了这个方法（因为可以正确地调用这个方法的派生类版本），此外还证明如果基类将方法设置为 public，就无法完整地强制访问 protected 方法。</p>
<blockquote>
<p><strong>注意：</strong><br>
无法（也没有很好的理由）限制访问基类的 public 方法。</p>
</blockquote>
<hr>
<blockquote>
<p><strong>注意：</strong></p>
<p>上例重新定义了派生类中的方法，因为它希望显示另一条消息。如果不希望修改实现，指向改变方法的访问级别，首选方法是在具有所需访问级别的派生类定义中添加 using 语句。</p>
</blockquote>
<p>在派生类中放宽访问限制就比较容易（也更有意义）。最简单的方法是提供一个 public 方法来调用基类的 protected 方法，如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Secret</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-protected">protected</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span> <span class="token function">dontTell</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"I'll never tell."</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Blabber</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Secret</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span> <span class="token function">tell</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">dontTell</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>调用 Blabber 对象的 public 方法 tell() 的客户代码可有效地访问 Secret 类的 protected 方法。当然，这并未整整改变 dontTell() 的访问级别，只是提供了访问这个方法的公共方式。</p>
<p>也可在 Blabber 派生类中显式地重写 dontTell()，并将这个方法设置为 public。这样做比降低访问级别更有意义，因为当使用基类指针或引用时，可以清楚地表明发生的事情。例如，假定 Blabber 类实际上将 dontTell() 方法设置为 public：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Blabber</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Secret</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-void">void</span> <span class="token function">dontTell</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-override">override</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"I'll tell all!"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>现在调用 Blabber 对象 的 dontTell() 方法：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Blabber myBlabber<span class="token punctuation">;</span>
myBlabber<span class="token punctuation">.</span><span class="token function">dontTell</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment">// Outputs "I'll tell all!"</span>
</code></pre><p>如果不想更改重写方法的实现，只想更改访问级别，可使用 using 子句。例如：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Blabber</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Secret</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-using">using</span> Secret<span class="token double-colon punctuation">::</span>dontTell<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>这也允许调用 Blabber 对象的 dontTell() 方法，但这一次，输出将会是“ I’ll never tell”。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>myBlabber<span class="token punctuation">.</span><span class="token function">dontTell</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment">// Outputs "I'll never tell!"</span>
</code></pre><p>然而，在上述情况下，基类中的 protected 方法仍然时受保护的，因为使用 Secret 指针或引用调用 Secret 类的 dontTell() 方法将无法通过编译。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Blabber myBlabber<span class="token punctuation">;</span>
Secret<span class="token operator">&amp;</span> ref<span class="token punctuation">{</span>myBlabber<span class="token punctuation">}</span><span class="token punctuation">;</span>
Secret<span class="token operator">*</span> ptr<span class="token punctuation">{</span><span class="token operator">&amp;</span>myBlabber<span class="token punctuation">}</span><span class="token punctuation">;</span>
ref<span class="token punctuation">.</span><span class="token function">dontTell</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 编译不通过</span>
ptr<span class="token operator">-&gt;</span><span class="token function">dontTell</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment">// 编译不通过</span>
</code></pre><blockquote>
<p><strong>注意：</strong></p>
<p>修改方法访问级别的唯一真正有用的方式是对 protected 方法提供较宽松的访问限制。</p>
</blockquote>
<h4 id="1065-派生类中的复制构造函数和赋值运算符">10.6.5 派生类中的复制构造函数和赋值运算符 </h4>
<p>第 9 章讲过，在类中使用动态内存分配时，提供复制构造函数和赋值运算符是良好的编程习惯。当定义派生类时，必须注意复制构造函数和 operator=。</p>
<p>如果派生类没有任何需要使用非默认复制构造函数或 operator= 的特殊数据（通常是指针），无论基类是否有这类数据，都不需要它们。如果派生类省略了复制构造函数或 operator=，派生了中指定的数据成员就使用默认的复制构造函数或 operator=，基类中的数据成员使用基类的复制构造函数或 operator=。</p>
<p>另外，如果在派生类中制定了复制构造函数，就需要显式地链接到父类的复制构造函数，下面的代码演示了这一内容。如果不这么做，将使用默认构造函数（不是复制构造函数！）初始化对象的父类部分。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Base</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">Base</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token function">Base</span><span class="token punctuation">(</span><span class="token punctuation">)</span>          <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token function">Base</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Base<span class="token operator">&amp;</span> src<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Derived</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Base</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">Derived</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token function">Derived</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Derived<span class="token operator">&amp;</span> src<span class="token punctuation">)</span>
        <span class="token operator">:</span> Base<span class="token punctuation">{</span>src<span class="token punctuation">}</span>
    <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>与此类似，如果派生类重写了 operator=，则几乎总是需要调用父类版本的 operator=。唯一的例外是因为某些奇怪的原因，在赋值时指向给对象的一部分赋值。下面的代码显示了如何在派生类中调用父类的赋值运算符。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Derived<span class="token operator">&amp;</span> Derived<span class="token double-colon punctuation">::</span><span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Derived<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span><span class="token operator">&amp;</span>rhs <span class="token operator">==</span> <span class="token keyword keyword-this">this</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-return">return</span> <span class="token operator">*</span><span class="token keyword keyword-this">this</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    Base<span class="token double-colon punctuation">::</span><span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span>rhs<span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment">// 调用父类的赋值运算符</span>
    <span class="token keyword keyword-return">return</span> <span class="token operator">*</span><span class="token keyword keyword-this">this</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><blockquote>
<p><strong>警告：</strong><br>
如果派生类不指定自己的复制构造函数或 operator=，基类的功能将继续运行，否则，就需要显式引用基类版本。</p>
</blockquote>
<hr>
<blockquote>
<p><strong>注意：</strong><br>
如果在继承层次结构中需要复制功能，专业 C++ 开发人员惯用的做法时实现多态 clone() 方法，因为不能完全依靠标准复制构造函数和复制运算符类满足需要。第 12 章“使用模板编写泛型代码”将讨论多态的 clone() 方法。</p>
</blockquote>
<h4 id="1066-运行期类型工具">10.6.6 运行期类型工具 </h4>
<p>相对于其他面向对象语言，C++ 以编译期为主。如前所述，重写方法时可行的，这是由于方法和实现之间的间隔，而不是由于对象有关于自身所属类的内建信息。</p>
<p>然而在 C++ 中，有些特性提供了对象的运行期视角。这些特性通常归属于一个名为运行期类型信息（Run Time Type Information, RTTI）的特性集。RTTI 提供了许多有用的特性，用于判断对象所属的类。其中一个特性时本章前面说过的 dynamic_cast()，可以在 OO 层次结构中进行安全的类型转换，本章前面讨论过这一点。如果使用类上的 dynamic_cast()，但没有虚表，即没有虚方法，将导致编译错误。</p>
<p>RTTI 的第二个特性是 typeid 运算符，这个运算符可在运行期查询对象，从而判别对象的类型。大多数情况下，不应该使用 typeid，因为最好用虚方法处理基于对象类型运行的代码。下面的代码使用了 typeid，根据对象的类型输出消息。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;iostream&gt;</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;typeinfo&gt;</span></span>

<span class="token keyword keyword-using">using</span> <span class="token keyword keyword-namespace">namespace</span> std<span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Animal</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token operator">~</span><span class="token function">Animal</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-class">class</span> <span class="token class-name">Dog</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Animal</span></span>
<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-class">class</span> <span class="token class-name">BIrd</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Animal</span></span>
<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-void">void</span> <span class="token function">speak</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Animal<span class="token operator">&amp;</span> animal<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span><span class="token keyword keyword-typeid">typeid</span><span class="token punctuation">(</span>animal<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token keyword keyword-typeid">typeid</span><span class="token punctuation">(</span>Dog<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Woff!"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword keyword-else">else</span> <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span><span class="token keyword keyword-typeid">typeid</span><span class="token punctuation">(</span>animal<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token keyword keyword-typeid">typeid</span><span class="token punctuation">(</span>BIrd<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Chirp!"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>一旦看到这样的代码，就应该立即考虑用虚方法重新实现该功能。在此情况下，更好的实现是在 Animal 类中声明一个 speak() 虚方法。Dog 类会重写这个方法，输出“Woof!”；Bird 类也会重写这个方法，输出“Chirp!”。这种方式更适合面向对象程序，会将与对象有关的功能给予这些对象。</p>
<blockquote>
<p><strong>警告：</strong></p>
<p>类至少有一个虚方法（即类具有一张虚函数表），typeid 运算符才能正常运行。另外，typeid 运算符也亏从实参中取出引用和 const 限定符。</p>
</blockquote>
<p>typeid 运算符的主要价值之一在于日志记录和调试。下面的代码将 typeid 用于日志记录。logObject() 函数将“可记录”对象作为参数。设计是这样的：任何可记录的对象都从 Loggable 类中继承，都支持 getLogMessage() 方法。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Loggable</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">Loggable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                       <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-virtual">virtual</span> std<span class="token double-colon punctuation">::</span>string <span class="token function">getLogMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Foo</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Loggable</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    std<span class="token double-colon punctuation">::</span>string <span class="token function">getLogMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token keyword keyword-override">override</span> <span class="token punctuation">{</span> <span class="token keyword keyword-return">return</span> <span class="token string">"Hello logger."</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-void">void</span> <span class="token function">logObject</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Loggable<span class="token operator">&amp;</span> loggableObject<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    cout <span class="token operator">&lt;&lt;</span> <span class="token keyword keyword-typeid">typeid</span><span class="token punctuation">(</span>loggableObject<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">name</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> <span class="token string">": "</span><span class="token punctuation">;</span>
    cout <span class="token operator">&lt;&lt;</span> loggableObject<span class="token punctuation">.</span><span class="token function">getLogMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>logObject() 函数首先将对象所属类的名称写道输出流，随后是日志信息。这样以后阅读日志时，就可以看出文件每一行涉及的对象。输出结果略。</p>
<blockquote>
<p><strong>注意：</strong></p>
<p>如果不是为了日志记录或调试而使用 typeid，应该考虑用虚方法替代 typeid。</p>
</blockquote>
<h4 id="1067-非-public-继承">10.6.7 非 public 继承 </h4>
<p>在前面的所有示例中，总是用 public 关键字列出父类。父类是否可以是 private 或 protected？实际上可以这样做，尽管二者并不像 public 那样普遍。如果没有为父类指定任何访问说明符，就说明是类的 private 继承、结构的 public 继承。</p>
<p>将父类的关系声明为 protected，意味着在派生类中，基类的所有 public 方法和数据成员都称为受保护的。与此类似，指定 private 继承意味着基类的所有 public、protected 方法和数据成员在派生类中都成为私有的。</p>
<p>使用这种方法统一降低父类的访问级别有许多原因，但多数原因都是层次结构的设计缺陷。有些程序员滥用这一语言特性，经常与多重继承一起实现类的“组件”。不是让 Airplane 类包含引擎数据成员和机身数据成员，而将 Airplane 类作为 protected 引擎和 protected 机身。这样，对于客户代码来说，Airplane 对象看上去并不像引擎或机身（因为一切都是受保护的），但在内部可以使用引擎和机身的功能。</p>
<blockquote>
<p><strong>注意：</strong></p>
<p>非 public 继承很少见，建议慎用这一特性，因为多数程序员并不熟悉它。</p>
</blockquote>
<h4 id="1068-虚基类">10.6.8 虚基类 </h4>
<p>本章前面学习了歧义父类，当多个基类拥有公共父类时，就会发生这种情况，如图所示。</p>
<div class="mermaid">classDiagram
class Animal
class Dog
class Bird
class DogBird

Animal &lt;|-- Dog
Animal &lt;|-- Bird
Dog &lt;|-- DogBird
Bird &lt;|-- DogBird
</div><p>建议的解决方案是让共享的父类本省没有任何自有功能。这样就永远无法调用这个类的方法，因此也就不存在歧义。</p>
<p>如果希望被共享的父类拥有自己的功能，C++ 提供了另一种机制来解决这个问题。如果被共享的基类是一个虚基类（virtual base class），就不存在歧义。以下代码在 Animal 基类中添加了 sleep() 方法，并修改了 Dog 和 Bird 类，从而将 Animal 作为虚基类继承。如果不使用 virtual 关键字，用 DogBird 对象调用 sleep() 会产生歧义，从而导致编译错误。因为 DogBird 对象有 Animal 类的两个子对象，一个来自 Dog 类，另一个来自 Bird 类。然而，如果 Animal 被作为虚基类，DogBird 对象就只有 Animal 类的一个子对象，因此调用 sleep() 也就不存在歧义。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;iostream&gt;</span></span>

<span class="token keyword keyword-using">using</span> <span class="token keyword keyword-namespace">namespace</span> std<span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Animal</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span> <span class="token function">eat</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"zzzzz...."</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Dog</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token keyword keyword-virtual">virtual</span> <span class="token class-name">Animal</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span> <span class="token function">bark</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Woof!"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword keyword-void">void</span>         <span class="token function">eat</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-override">override</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"The dog ate."</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Bird</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token keyword keyword-virtual">virtual</span> <span class="token class-name">Animal</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token keyword keyword-void">void</span> <span class="token function">chirp</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Chirp!"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword keyword-void">void</span>         <span class="token function">eat</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-override">override</span> <span class="token punctuation">{</span> cout <span class="token operator">&lt;&lt;</span> <span class="token string">"The bird ate."</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">DogBird</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Dog</span><span class="token punctuation">,</span> <span class="token keyword keyword-public">public</span> <span class="token class-name">Bird</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-void">void</span> <span class="token function">eat</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-override">override</span> <span class="token punctuation">{</span> <span class="token class-name">Dog</span><span class="token double-colon punctuation">::</span><span class="token function">eat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-int">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    DogBird myConfusedAnimal<span class="token punctuation">;</span>
    myConfusedAnimal<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><blockquote>
<p><strong>注意：</strong></p>
<p>虚基类是在类层次结构中避免歧义的好办法。唯一的缺点是许多 C++ 程序员不熟悉这个概念。</p>
</blockquote>
<h3 id="107-类型转换">10.7 类型转换 </h3>
<p>第 1 章介绍了 C++ 中的基本类型，而第 8~10 章介绍了使用类编写自定义的类型。本届将探讨如何将一种类型转换为另一种类型。</p>
<p>C++ 提供了 5 种特定的强制类型转换：<em><strong>const_cast()、static_cast()、reinterpret_cast()、dynamic_cast() 和 std::bit_cast()</strong></em>（从 C++20 开始）。const_cast() 在第一章进行了讨论。第 1 章还介绍了 static_cast()，用于在某些基本类型之间进行转换，但在继承的上下文中，还有更多内容要介绍。监狱已经能够熟练地编写类并了解类的继承，现在仔细研究这些强制类型转换。</p>
<p>注意，旧的 C 风格的强制类型转换仍可以在 C++ 中使用，并且仍在现有代码库中广泛使用。C 风格的强制类型转换涵盖了所有 4 中 C++ 强制类型转换，因此它们更容易出错，因为要实现的目标并不总是很明显，并且最终可能会带来意想不到的结果。强烈建议仅在新代码中使用 C++ 风格的强制转换，因为它们更安全，并且在代码中在语法上更加突出。</p>
<h4 id="1071-static_cast">10.7.1 static_cast() </h4>
<p>可以使用 static_cast() 执行语言直接支持的显式转换。比如，如果编写一个算术表达式，需要将 int 转换为 double 以避免整数除法，则可以使用 static_cast()。在本例中，只对 i 使用 static_cast() 就足够了，因为这回事两个操作数中的一个变成双精度，从而确保 C++ 执行浮点数除法。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-int">int</span>    i<span class="token punctuation">{</span><span class="token number">3</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-int">int</span>    j<span class="token punctuation">{</span><span class="token number">4</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-double">double</span> result<span class="token punctuation">{</span><span class="token generic-function"><span class="token function">static_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token keyword keyword-double">double</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>i<span class="token punctuation">)</span> <span class="token operator">/</span> j<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>还可以使用 static_cast() 执行显式转换，这是用户定义的构造函数或转换例程允许的。比如，如果类 A 有一个接受 B 对象的构造函数，则可以使用 static_cast() 将 B 对象转换为 A 对象。但是，在大多数情况下，需要这种行为时，编译器会自动执行转换。</p>
<p>static_cast() 的另一个用途是在继承层次结构中执行向下强制转换，示例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Base<span class="token operator">*</span>    b<span class="token punctuation">{</span><span class="token keyword keyword-nullptr">nullptr</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
Derived<span class="token operator">*</span> d<span class="token punctuation">{</span><span class="token keyword keyword-new">new</span> Derived<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
b <span class="token operator">=</span> d<span class="token punctuation">;</span>
d <span class="token operator">=</span> <span class="token generic-function"><span class="token function">static_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span>Derived<span class="token operator">*</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">;</span>

Base     base<span class="token punctuation">;</span>
Derived  derived<span class="token punctuation">;</span>
Base<span class="token operator">&amp;</span>    br<span class="token punctuation">{</span>derived<span class="token punctuation">}</span><span class="token punctuation">;</span>
Derived<span class="token operator">&amp;</span> dr<span class="token punctuation">{</span><span class="token generic-function"><span class="token function">static_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span>Derived<span class="token operator">&amp;</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>br<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>这些强制类型转换既可以使用指针也可以使用引用。它们不使用对象。</p>
<p>注意，使用 static_cast() 的强制类型转换不会执行运行期类型检查，它们允许将任何 Base 指针转换为 Derived 指针，或是将 Base 引用转换为 Derived 引用。即使在运行期，Base 实际上不是 Derived 也是如此。比如编译并执行以下代码，但使用指针 d 可能会导致潜在的灾难性故障，包括在对象边界之外进行内存覆盖。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Base<span class="token operator">*</span>    b<span class="token punctuation">{</span><span class="token keyword keyword-new">new</span> Base<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
Derived<span class="token operator">*</span> d<span class="token punctuation">{</span><span class="token generic-function"><span class="token function">static_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span>Derived<span class="token operator">*</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">}</span>
</code></pre><p>要在运行期检查时安全地执行转换，请使用 dynamic_cast()，本章稍后对此进行说明。</p>
<p>static_cast() 不是万能的。不能将一种类型的指针 static_cast() 转换为另一种不相关类型的指针；如果没有可用的转换构造函数，则无法将一种类型的对象直接 static_cast() 转换为另种类型的对象；不能将常量类型的 static_cast() 转换为非常量类型；不能将指针 static_cast() 转换为整数。基本上，根据 C++ 的类型规则，不能做任何没有意义的事情。</p>
<h4 id="1072-reinterpret_cast">10.7.2 reinterpret_cast() </h4>
<p>与 static_cast() 相比，reinterpret_cast() 的功能更强大，同时安全性也更低。可以使用它执行 C++ 类型规则中在技术上不允许的某些强制转换，但在某些情况下，这对程序员来说可能是由意义的。比如，即使类型不相关，也可以将一种类型的引用强制转换为对另一种类型的引用。同样，可以将指针类强制转换为任何其他指针类型，即使它们与继承层次结构无关。这通常用于将指针强制转换为 void* 类型的指针。这可以隐式完成，因此不需要显式强制转换。但是，将 void* 强制转换回正确类型的指针需要 reinterpret_cast()。void* 类型的指针只是指向内存中的某个位置。没有类型信息与 void* 类型的指针相关联。示例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">X</span>
<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-class">class</span> <span class="token class-name">Y</span>
<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-int">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    X  x<span class="token punctuation">;</span>
    Y  y<span class="token punctuation">;</span>
    X<span class="token operator">*</span> xp<span class="token punctuation">{</span><span class="token operator">&amp;</span>x<span class="token punctuation">}</span><span class="token punctuation">;</span>
    Y<span class="token operator">*</span> yp<span class="token punctuation">{</span><span class="token operator">&amp;</span>y<span class="token punctuation">}</span><span class="token punctuation">;</span>

    xp <span class="token operator">=</span> <span class="token generic-function"><span class="token function">reinterpret_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span>X<span class="token operator">*</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>yp<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-void">void</span><span class="token operator">*</span> p<span class="token punctuation">{</span>xp<span class="token punctuation">}</span><span class="token punctuation">;</span>
    xp <span class="token operator">=</span> <span class="token generic-function"><span class="token function">reinterpret_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span>X<span class="token operator">*</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>p<span class="token punctuation">)</span><span class="token punctuation">;</span>
    X<span class="token operator">&amp;</span> xr<span class="token punctuation">{</span>x<span class="token punctuation">}</span><span class="token punctuation">;</span>
    Y<span class="token operator">&amp;</span> yr<span class="token punctuation">{</span><span class="token generic-function"><span class="token function">reinterpret_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span>Y<span class="token operator">&amp;</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>reinterpret_cast() 并非万能；它对可以转换为什么有很多限制。这些限制在本书不会进一步讨论，因为建议谨慎使用这些类型的强制类型转换。通常，应谨慎使用 reinterpret_cast()，因为它允许在不执行任何类型检查的情况下进行转换。</p>
<blockquote>
<p><strong>警告：</strong></p>
<p>还<strong>可以使用 reinterpret_cast() 将指针转换为整数类型并返回</strong>。但是，只能将指针转换为足够容纳它的整数类型。比如，如果使用 reinterpret_cast() 将 64 位指针强制转换为 32 位整数，会导致编译错误。</p>
</blockquote>
<h4 id="1073-stdbit_cast">10.7.3 std::bit_cast() </h4>
<p>C++ 20 引入了 std::bit_cast()，它定义在 &lt;bit&gt; 中。这是标准库中唯一的强制类型转换；其他强制转换是 C++ 语言本身的一部分。bit_cast() 与 reinterpret_cast() 类似，但它会创建一个指定目标类型的新对象，并<strong>按位</strong>从源对象复制到此新对象。它有效地将源对象的位解释为目标对象的位。bit_cast() 要求源对象和目标对象的大小相同，并且两者都是可复制的。示例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-float">float</span> asFloat<span class="token punctuation">{</span><span class="token number">1.23f</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-auto">auto</span>  asUint<span class="token punctuation">{</span><span class="token generic-function"><span class="token function">bit_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token keyword keyword-unsigned">unsigned</span> <span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>asFloat<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span><span class="token generic-function"><span class="token function">bit_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token keyword keyword-float">float</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>asUint<span class="token punctuation">)</span> <span class="token operator">==</span> asFloat<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Roundtrip success."</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><blockquote>
<p><strong>注意：</strong></p>
<p>普通可复制类型是，组成对象的底层字节可以复制到一个新组中（比如 char），如果数组的数据随后被复制回对象，则该对象将保持其原始值。</p>
</blockquote>
<p>bit_cast() 的一个用例是普通可复制类型的二进制 I/O。比如，可以将此类型的各个字节写入文件。当文件读回到内存时，可以使用 bit_cast() 正确地解释从文件中读取的字节。</p>
<h4 id="1074-dynamic_cast">10.7.4 dynamic_cast() </h4>
<p>dynamic_cast() 提供了对继承层次结构中的强制转换的运行期检查。可以使用它强制转换指针或引用。dynamic_cast() 在运行期检查底层对象的运行期类型信息。如果强制转换无效，dynamic_cast() 将返回空指针（对于指针），或者抛出 std::bad_cast 异常（对于引用）。<br>
比如，假设有以下类层次结构：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Base</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">Base</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Derived</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Base</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">Derived</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>下面的示例演示了 dynamic_cast() 的正确使用：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Base<span class="token operator">*</span>    b<span class="token punctuation">;</span>
Derived<span class="token operator">*</span> d<span class="token punctuation">{</span><span class="token keyword keyword-new">new</span> Derived<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
b <span class="token operator">=</span> d<span class="token punctuation">;</span>
d <span class="token operator">=</span> <span class="token generic-function"><span class="token function">dynamic_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span>Derived<span class="token operator">*</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>下面对于引用的 dynamic_cast() 将抛出异常：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Base</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">Base</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Derived</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">Base</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">Derived</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-int">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    Base<span class="token operator">*</span>    b<span class="token punctuation">;</span>
    Derived<span class="token operator">*</span> d<span class="token punctuation">{</span><span class="token keyword keyword-new">new</span> Derived<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    b <span class="token operator">=</span> d<span class="token punctuation">;</span>
    d <span class="token operator">=</span> <span class="token generic-function"><span class="token function">dynamic_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span>Derived<span class="token operator">*</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">;</span>

    Base    base<span class="token punctuation">;</span>
    Derived derived<span class="token punctuation">;</span>
    Base<span class="token operator">&amp;</span>   br<span class="token punctuation">{</span>base<span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-try">try</span> <span class="token punctuation">{</span>
        Derived<span class="token operator">&amp;</span> dr<span class="token punctuation">{</span><span class="token generic-function"><span class="token function">dynamic_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span>Derived<span class="token operator">&amp;</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>br<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword keyword-catch">catch</span> <span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> bad_cast<span class="token operator">&amp;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Bad cast!"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>注意，可以使用 static_cast() 或 reinterpret_cast() 在继承层次上执行相同的强制转换。与 dynamic_cast() 的区别在于：<strong>dynamic_cast() 执行运行期（动态）类型检查，而 static_cast() 和 reinterpret_cast() 则执行强制转换</strong>，即使转换的类型是错误的。</p>
<p>请记住，运行期的类型信息存储在对象的 vtable 中。因此，要使用 dynamic_cast()，类中必须<strong>至少具有一个虚方法</strong>。如果类没有 vtable，尝试使用 dynamic_cast() 会导致编译错误。</p>
<h4 id="1075-类型转换小结">10.7.5 类型转换小结 </h4>
<p>下表总结了在不同情况下应使用的强制类型转换。</p>
<table>
<thead>
<tr>
<th>场景</th>
<th>类型转换</th>
</tr>
</thead>
<tbody>
<tr>
<td>移除 const 属性</td>
<td>const_cast()</td>
</tr>
<tr>
<td>语言支持的显式强制转换（比如，将 int 转换为 double，将 int 转换为 bool））</td>
<td>static_cast()</td>
</tr>
<tr>
<td>用户定义的构造函数或转换函数支持的显式强制转换</td>
<td>static_cast()</td>
</tr>
<tr>
<td>一个类的对象转换为另一个（无关）类的对象</td>
<td>bit_cast()</td>
</tr>
<tr>
<td>在同一继承层次结构中，一个类的指针转换为另一个类的指针</td>
<td>建议 dynamic_cast()，或 static_cast()</td>
</tr>
<tr>
<td>在同一继承层次结构中，一个类的指针转换为另一个类的引用</td>
<td>建议 dynamic_cast()，或 static_cast()</td>
</tr>
<tr>
<td>指向一种类型的指针转换为指向其他不相关类型的指针</td>
<td>reinterpret_cast()</td>
</tr>
<tr>
<td>一种类型的引用转换为其他不相关的类型的引用</td>
<td>reinterpret_cast()</td>
</tr>
<tr>
<td>指向函数的指针转换为指向函数的指针</td>
<td>reinterpret_cast()</td>
</tr>
</tbody>
</table>
<h2 id="11-零碎的工作">11 零碎的工作 </h2>
<h3 id="111-模块">11.1 模块 </h3>
<h4 id="1111-模块接口文件">11.1.1 模块接口文件 </h4>
<p>模块接口文件，是为模块提供的功能定义的接口。模块接口文件通常以 .cppm  作为扩展名。模块接口以声明开头，声明该文件正在定义一个具有特定名称的模块。这称为模块声明。模块的名称可以是任何有效的 C++ 标识符。名称中可以包含点（.），但不能以点（.）开头或结尾，也不能在一行中包含多个点（.）。</p>
<p>模块需要显示地声明要导出的内容，即当用户代码导入模块时，哪些内容应该时可见的。使用 export 关键字从模块中导出实体（比如类、函数、常量、其他模块等）。任何没有从模块中导出的内容，只在该模块中可见。所有导出实体的集合称为模块接口。</p>
<p>下面是一个名为 Person.cppm 的模块接口文件的示例，它定义了 person 模块，并导出了 person 类。注意它导入了 &lt;string&gt; 提供的功能。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">ifdef</span> <span class="token expression">WRITE_IN_VSCODE </span><span class="token comment">//这个是自己写的，因为 vscode 不支持模块的代码补全</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;string&gt;</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-module">module</span> <span class="token module">person</span><span class="token punctuation">;</span>

<span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;string&gt;</span></span><span class="token punctuation">;</span>

<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-class">class</span> <span class="token class-name">Person</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">Person</span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span>string firstName<span class="token punctuation">,</span> std<span class="token double-colon punctuation">::</span>string lastName<span class="token punctuation">)</span>
        <span class="token operator">:</span> m_firstName<span class="token punctuation">{</span>std<span class="token double-colon punctuation">::</span><span class="token function">move</span><span class="token punctuation">(</span>firstName<span class="token punctuation">)</span><span class="token punctuation">}</span>
        <span class="token punctuation">,</span> m_lastName<span class="token punctuation">{</span>lastName<span class="token punctuation">}</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token keyword keyword-const">const</span> std<span class="token double-colon punctuation">::</span>string<span class="token operator">&amp;</span> <span class="token function">getFirstName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token punctuation">{</span> <span class="token keyword keyword-return">return</span> m_firstName<span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword keyword-const">const</span> std<span class="token double-colon punctuation">::</span>string<span class="token operator">&amp;</span> <span class="token function">getLastName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token punctuation">{</span> <span class="token keyword keyword-return">return</span> m_lastName<span class="token punctuation">;</span> <span class="token punctuation">}</span>

<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    std<span class="token double-colon punctuation">::</span>string m_firstName<span class="token punctuation">;</span>
    std<span class="token double-colon punctuation">::</span>string m_lastName<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>可以通过以下方式导入 Person 模块，来使用 Person 类（test.cpp）</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">ifdef</span> <span class="token expression">WRITE_IN_VSCODE</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;iostream&gt;</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;string&gt;</span></span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;Person.ixx&gt;</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token keyword keyword-import">import</span> <span class="token module">person</span><span class="token punctuation">;</span>
<span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;iostream&gt;</span></span><span class="token punctuation">;</span>
<span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;string&gt;</span></span><span class="token punctuation">;</span>

<span class="token keyword keyword-using">using</span> <span class="token keyword keyword-namespace">namespace</span> std<span class="token punctuation">;</span>

<span class="token keyword keyword-int">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    Person person<span class="token punctuation">{</span><span class="token string">"Kole"</span><span class="token punctuation">,</span> <span class="token string">"Webb"</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    cout <span class="token operator">&lt;&lt;</span> person<span class="token punctuation">.</span><span class="token function">getLastName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> <span class="token string">", "</span> <span class="token operator">&lt;&lt;</span> person<span class="token punctuation">.</span><span class="token function">getLastName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>所有的 C++ 头文件（比如 &lt;iostream&gt;、&lt;vector&gt;、&lt;string&gt; 等），都是所谓的可导入头文件，可以通过 import 声明导入。正如第 1 章介绍的，C++ 中可用的 C 的头文件不能保证是可导入的。为了安全起见，<strong>在 C 的头文件中使用 #include 替换 import 声明</strong>。这样的 #include 指令应该放在所谓的<strong>全局模块片段</strong>中，它<strong>必须出现在任何命名模块的声明之前</strong>，以匿名模块的声明开始，<strong>全局模块片段只能包含预处理指令</strong>，比如 #include。这样的全局模块片段和注释是唯一允许出现在命名模块声明之前的内容。比如，如果需要使用 C 的头文件 &lt;cstddef&gt; 中的功能，可以如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-module">module</span><span class="token punctuation">;</span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;cstddef&gt;</span></span>

<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-module">module</span> <span class="token module">person</span><span class="token punctuation">;</span>

<span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;string&gt;</span></span><span class="token punctuation">;</span>

<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-class">class</span> <span class="token class-name">Person</span>
<span class="token punctuation">{</span>
    <span class="token comment">/* 其余省略 */</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>在标准术语中，从已命名模块声明到文件末尾的所有内容称为模块权限。</p>
<p>几乎所有内容都可以从模块导出，只要它有一个名称。比如类定义、函数原型、类枚举类型、using 声明和指令、名称空间等。如果使用 export 关键字显式导出名称空间，则该名称空间中的所有内容也将自动导出。比如，下面的代码片段导出整个 DataModel 名称空间；因此，没有必要显示地导出单个类和类型别名：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-export">export</span> <span class="token keyword keyword-module">module</span> <span class="token module">datamodel</span><span class="token punctuation">;</span>
<span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;vector&gt;</span></span><span class="token punctuation">;</span>
<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-namespace">namespace</span> DataModel <span class="token punctuation">{</span>
<span class="token keyword keyword-class">class</span> <span class="token class-name">Person</span>
<span class="token punctuation">{</span> <span class="token comment">/*  */</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-class">class</span> <span class="token class-name">Address</span>
<span class="token punctuation">{</span> <span class="token comment">/*  */</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-using">using</span> Persons <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span>vector<span class="token operator">&lt;</span>Person<span class="token operator">&gt;</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>   <span class="token comment">// namespace DataModel</span>
</code></pre><p>还可以使用导出块（export block）导出整个声明块。示例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-export">export</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-namespace">namespace</span> DataModel <span class="token punctuation">{</span>
    <span class="token keyword keyword-class">class</span> <span class="token class-name">Person</span>
    <span class="token punctuation">{</span> <span class="token comment">/*  */</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-class">class</span> <span class="token class-name">Address</span>
    <span class="token punctuation">{</span> <span class="token comment">/*  */</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-using">using</span> Persons <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span>vector<span class="token operator">&lt;</span>Person<span class="token operator">&gt;</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>   <span class="token comment">// namespace DataModel</span>
<span class="token punctuation">}</span>
</code></pre><h4 id="1112-模块实现文件">11.1.2 模块实现文件 </h4>
<p>一个模块可以被拆分为一个模块接口文件和一个或多个模块实现文件。模块实现文件通常以 .cpp 作为扩展名。可以自由决定将哪些实现移到模块实现文件中，以及将哪些实现留在模块接口文件中。一种选择是将所有函数和方法实现移到模块实现文件中，而将其他函数和方法的实现移到实现文件中。这里有很大的灵活性。</p>
<p>模块实现文件中同样包含一个已命名模块的声明，用于指定实现所针对的模块，但没有 export 关键字。比如，可以将前面的 person 模块拆分为接口和实现文件，如下所示。下面是模块接口文件：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">ifdef</span> <span class="token expression">WRITE_IN_VSCODE</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;string&gt;</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-module">module</span> <span class="token module">person</span><span class="token punctuation">;</span>

<span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;string&gt;</span></span><span class="token punctuation">;</span>

<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-class">class</span> <span class="token class-name">Person</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">Person</span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span>string firstName<span class="token punctuation">,</span> std<span class="token double-colon punctuation">::</span>string lastName<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-const">const</span> std<span class="token double-colon punctuation">::</span>string<span class="token operator">&amp;</span> <span class="token function">getFirstName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-const">const</span> std<span class="token double-colon punctuation">::</span>string<span class="token operator">&amp;</span> <span class="token function">getLastName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>

<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    std<span class="token double-colon punctuation">::</span>string m_firstName<span class="token punctuation">;</span>
    std<span class="token double-colon punctuation">::</span>string m_lastName<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>实现现在位于模块实现文件 Person.cpp 中：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">ifdef</span> <span class="token expression">WRITE_IN_VSCODE</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;Person.ixx&gt;</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token keyword keyword-module">module</span> <span class="token module">person</span><span class="token punctuation">;</span>

<span class="token keyword keyword-using">using</span> <span class="token keyword keyword-namespace">namespace</span> std<span class="token punctuation">;</span>

<span class="token class-name">Person</span><span class="token double-colon punctuation">::</span><span class="token function">Person</span><span class="token punctuation">(</span>string firstName<span class="token punctuation">,</span> string lastName<span class="token punctuation">)</span>
    <span class="token operator">:</span> m_firstName<span class="token punctuation">{</span><span class="token function">move</span><span class="token punctuation">(</span>firstName<span class="token punctuation">)</span><span class="token punctuation">}</span>
    <span class="token punctuation">,</span> m_lastName<span class="token punctuation">{</span><span class="token function">move</span><span class="token punctuation">(</span>lastName<span class="token punctuation">)</span><span class="token punctuation">}</span>
<span class="token punctuation">{</span><span class="token punctuation">}</span>

<span class="token keyword keyword-const">const</span> string<span class="token operator">&amp;</span> <span class="token class-name">Person</span><span class="token double-colon punctuation">::</span><span class="token function">getFirstName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> m_firstName<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword keyword-const">const</span> string<span class="token operator">&amp;</span> <span class="token class-name">Person</span><span class="token double-colon punctuation">::</span><span class="token function">getLastName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> m_lastName<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>注意，实现文件没有 person 模块的导入声明。module person 的声明隐式包含 import person 的声明。还要注意的是，实现文件中没有 &lt;string&gt; 的导入声明，即使它在方法的实现中使用了 std::string。正是由于隐式的 import person，并且因为这个实现文件是同一个 person 模块的一部分，所以它隐式地从模块接口文件中继承了 &lt;string&gt; 的导入声明。与之相反，向 test.cpp 文件添加 import person 的声明并不会隐式地继承 &lt;string&gt; 的导入声明，因为 test.cpp 不是 person 模块的一部分。关于这一点还有很多展开的，这是即将到来的“可见性与可访问性”章节的主题。</p>
<blockquote>
<p><strong>注意：</strong></p>
<p>模块接口和模块实现中的导入声明都必须位于文件的顶部，在已命名模块声明之后，但需要在任何其他声明之前。</p>
</blockquote>
<p>类似于模块接口文件，如果需要在模块实现文件中，使用 #include 指令导入已有的头文件，则应该将他们放在全局模块片段中，并使用与模块接口文件相同的语法。</p>
<blockquote>
<p><strong>警告：</strong></p>
<p>模块实现文件不能导出任何内容，只有模块接口文件可以。</p>
</blockquote>
<h4 id="1113-从实现中分离接口">11.1.3 从实现中分离接口 </h4>
<p>当使用头文件而不是模块时，强烈建议<strong>只把声明放在头文件（.h）中</strong>，并将<strong>所有实现移到源文件（.cpp）中</strong>。原因之一时可以<strong>缩短编译时间</strong>。如果将实现放在头文件中，任何的代码变更，哪怕是修改注释，都需要重新编译包含该头文件的其他所有源文件，对于某些头文件，这可能会影响整个代码库，导致整个程序完全编译。将实现放到源文件中，在不涉及头文件的情况下，对这些实现进行更改，这样只需要重新编译单个源文件。</p>
<p>模块的工作方式是不同的。模块接口（module interface）仅由类定义、函数原型等组成，虽然这些实现在模块接口文件中，但模块接口中不包含任何函数或方法的实现。因此，只要变更不涉及到模块接口的部分，变更模块接口中的函数/方法的实现是不需要重新编译该模块的，比如函数头（函数名、参数列表和返回类型），但有两个例外，使用 inline 关键字标记的函数或方法和模板的定义。对于这两者，编译器需要在编译使用它们的用户代码时知道其完整实现。因此，内联函数/方法或模板定义的任何变更都可能引发用户代码的重新编译。</p>
<blockquote>
<p><strong>注意：</strong></p>
<p>当头文件中的类定义中包含方法的实现，那么这些方法是隐式的内联，尽管没有使用 inline 关键字标识它们。对于在模块接口文件中的类定义里的方法来说，却又不一样了。如果要变成内联方法，就需要使用 inline 关键字显式标识它们。</p>
</blockquote>
<p>尽管从技术上讲，不再需要将接口从实现中分离，但在某些情况下，作者仍建议这么做。其主要目的是创建整洁和易读的接口。只要不会模糊接口和不会增加用户快速掌握公共接口提供的内容的难度，函数的实现就可以保留在接口中。例如，如果一个模块有一个规模很大的公共接口，那么最好不要使用模糊的接口实现，这样可以更好地了解所提供的内容。尽管如此，规模小的 getter 和 setter 函数仍然可以保留在接口中，因为不会影响接口的可读性。</p>
<p>将接口从实现中分离可以通过几种方式实现。一种是将模块拆分为接口文件和实现文件，如上一节所讨论的。另一种是在单个模块的接口文件中拆分接口和实现。例如，下面是定义在单个模块接口文件（person.cppm）中的 Person 类，但该类的实现是从接口中分离的。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">ifdef</span> <span class="token expression">WRITE_IN_VSCODE</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;string&gt;</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-module">module</span> <span class="token module">person</span><span class="token punctuation">;</span>

<span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;string&gt;</span></span><span class="token punctuation">;</span>

<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-class">class</span> <span class="token class-name">Person</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">Person</span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span>string firstName<span class="token punctuation">,</span> std<span class="token double-colon punctuation">::</span>string lastName<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-const">const</span> std<span class="token double-colon punctuation">::</span>string<span class="token operator">&amp;</span> <span class="token function">getFirstName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-const">const</span> std<span class="token double-colon punctuation">::</span>string<span class="token operator">&amp;</span> <span class="token function">getLastName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>

<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    std<span class="token double-colon punctuation">::</span>string m_firstName<span class="token punctuation">;</span>
    std<span class="token double-colon punctuation">::</span>string m_lastName<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>


<span class="token class-name">Person</span><span class="token double-colon punctuation">::</span><span class="token function">Person</span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span>string firstName<span class="token punctuation">,</span> std<span class="token double-colon punctuation">::</span>string lastName<span class="token punctuation">)</span>
    <span class="token operator">:</span> m_firstName<span class="token punctuation">{</span>std<span class="token double-colon punctuation">::</span><span class="token function">move</span><span class="token punctuation">(</span>firstName<span class="token punctuation">)</span><span class="token punctuation">}</span>
    <span class="token punctuation">,</span> m_lastName<span class="token punctuation">{</span>std<span class="token double-colon punctuation">::</span><span class="token function">move</span><span class="token punctuation">(</span>lastName<span class="token punctuation">)</span><span class="token punctuation">}</span>
<span class="token punctuation">{</span><span class="token punctuation">}</span>

<span class="token keyword keyword-const">const</span> std<span class="token double-colon punctuation">::</span>string<span class="token operator">&amp;</span> <span class="token class-name">Person</span><span class="token double-colon punctuation">::</span><span class="token function">getFirstName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> m_firstName<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword keyword-const">const</span> std<span class="token double-colon punctuation">::</span>string<span class="token operator">&amp;</span> <span class="token class-name">Person</span><span class="token double-colon punctuation">::</span><span class="token function">getLastName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> m_lastName<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h4 id="可见性和可访问性">可见性和可访问性 </h4>
<p>正如前面介绍的，当在不属于 person 模块的另一个源文件（比如 test.cpp 文件）中导入 person 模块时，不会隐式地从 person 模块的接口文件中继承 &lt;string&gt; 的导入声明。因此，如果在 test.cpp 中没有显式导入 &lt;string&gt;，那么 std::string 是不可见的，这意味着以下代码将无法正常编译。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-import">import</span> <span class="token module">person</span><span class="token punctuation">;</span>

<span class="token keyword keyword-int">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    std<span class="token double-colon punctuation">::</span>string        str<span class="token punctuation">;</span>   <span class="token comment">// 编译不通过</span>
    Person             person<span class="token punctuation">{</span><span class="token string">"Kole"</span><span class="token punctuation">,</span> <span class="token string">"Webb"</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-const">const</span> std<span class="token double-colon punctuation">::</span>string<span class="token operator">&amp;</span> lastName<span class="token punctuation">{</span>person<span class="token punctuation">.</span><span class="token function">getLastName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>   <span class="token comment">// 编译不通过</span>
<span class="token punctuation">}</span>
</code></pre><p>尽管如此，即使在 test.cpp 中没有显式导入 &lt;string&gt;，下面的代码也可以正常工作。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-const">const</span> <span class="token keyword keyword-auto">auto</span><span class="token operator">&amp;</span> lastName<span class="token punctuation">{</span>person<span class="token punctuation">.</span><span class="token function">getLastName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-auto">auto</span>        length<span class="token punctuation">{</span>lastName<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>为什么会这样呢？在 C++ 中，实体的可见性（visibility）和可访问性（reachability）是有区别的。通过导入 person 模块，&lt;string&gt; 中的功能变得可访问，但并不可见；可访问类的成员函数自动变得可见。所有这些都意味着可以使用 &lt;string&gt; 中的某些功能，比如使用自动类型推断（auto type deduction）和调用 length() 等方法，将 getLastName() 的结果存储在变量中。</p>
<p>为了使 std::string 名称在 test.cpp 中可见，需要显式导入 &lt;string&gt;。当想要使用（比如下面代码中的运算符 &lt;&lt;）时，这样的显式导入是必要的。这是因为运算符 &lt;&lt; 不是 std::string 的方法，而是只有通过导入 &lt;string&gt; 才可见的非成员函数。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>cout <span class="token operator">&lt;&lt;</span> person<span class="token punctuation">.</span><span class="token function">getLastName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
</code></pre><h4 id="1115-子模块">11.1.5 子模块 </h4>
<p>C++ 标准中并没有描述子模块（submodule）；但是，标准允许在模块名中使用符号点（.），这就可以在任何想要的层次结构中构建模块。比如，之前的章节给出的名称空间 DataModel 示例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">ifdef</span> <span class="token expression">WRITE_IN_VSCODE</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;vector&gt;</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-module">module</span> <span class="token module">DataModel</span><span class="token punctuation">;</span>
<span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;vector&gt;</span></span><span class="token punctuation">;</span>

<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-namespace">namespace</span> DataModel <span class="token punctuation">{</span>
<span class="token keyword keyword-class">class</span> <span class="token class-name">Person</span>
<span class="token punctuation">{</span> <span class="token comment">/*  */</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Address</span>
<span class="token punctuation">{</span> <span class="token comment">/*  */</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-using">using</span> Persons <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span>vector<span class="token operator">&lt;</span>Person<span class="token operator">&gt;</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>   <span class="token comment">// namespace DataModel</span>
</code></pre><p>类 Person 和类 Address 都位于名称空间 DataModel 和模块 datamodel 中。这可以通过定义两个子模块来重新进行构造：datamodel.person 和 datamodel.address。子模块 datamodel.person 的模块接口文件如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-export">export</span> <span class="token keyword keyword-module">module</span> <span class="token module">datamodel<span class="token punctuation">.</span>person</span><span class="token punctuation">;</span>

<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-namespace">namespace</span> DataModel <span class="token punctuation">{</span>
<span class="token keyword keyword-class">class</span> <span class="token class-name">Person</span>
<span class="token punctuation">{</span> <span class="token comment">/*  */</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>   <span class="token comment">// namespace DataModel</span>
</code></pre><p>下面是子模块 datamodel.address 的模块接口文件：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-export">export</span> <span class="token keyword keyword-module">module</span> <span class="token module">datamodel<span class="token punctuation">.</span>address</span><span class="token punctuation">;</span>

<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-namespace">namespace</span> DataModel <span class="token punctuation">{</span>
<span class="token keyword keyword-class">class</span> <span class="token class-name">Address</span>
<span class="token punctuation">{</span> <span class="token comment">/*  */</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>   <span class="token comment">// namespace DataModel</span>
</code></pre><p>最后，模块 datamodel 的定义如下。它导入并立即导出两个子模块：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">ifdef</span> <span class="token expression">WRITE_IN_VSCODE</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;vector&gt;</span></span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">"datamodel.address.ixx"</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">"datamodel.person.ixx"</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-module">module</span> <span class="token module">datamodel</span><span class="token punctuation">;</span>
<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-import">import</span> <span class="token module">datamodel<span class="token punctuation">.</span>person</span><span class="token punctuation">;</span>
<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-import">import</span> <span class="token module">datamodel<span class="token punctuation">.</span>address</span><span class="token punctuation">;</span>

<span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;vector&gt;</span></span><span class="token punctuation">;</span>

<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-namespace">namespace</span> DataModel <span class="token punctuation">{</span>
<span class="token keyword keyword-using">using</span> Persons <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span>vector<span class="token operator">&lt;</span>Person<span class="token operator">&gt;</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>当然，子模块中类的方法实现也可以放入模块实现文件中。比如，假设类 Address 有一个默认构造函数，它只打印一条语句到标准输出流中；这个实现可以放在文件 datamodel.address.cpp 中：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">ifdef</span> <span class="token expression">WRITE_IN_VSCODE</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;iostream&gt;</span></span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">"datamodel.address.ixx"</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token keyword keyword-module">module</span> <span class="token module">datamodel<span class="token punctuation">.</span>address</span><span class="token punctuation">;</span>
<span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;iostream&gt;</span></span><span class="token punctuation">;</span>
<span class="token keyword keyword-using">using</span> <span class="token keyword keyword-namespace">namespace</span> std<span class="token punctuation">;</span>

DataModel<span class="token double-colon punctuation">::</span><span class="token class-name">Address</span><span class="token double-colon punctuation">::</span><span class="token function">Address</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Address::Address()"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>使用子模块构造代码的好处是：用户可以导入一次他们想要使用的模块特定部分或全部内容。例如，如果用户只对类 Address 的使用感兴趣，那么下面的 import 声明就足够了。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-import">import</span> <span class="token module">datamodel<span class="token punctuation">.</span>address</span><span class="token punctuation">;</span>
</code></pre><p>另一方面，如果客户端代码需要访问模块 datamodel 中的所有内容，那么下面的导入声明是最简单的。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-import">import</span> <span class="token module">datamodel</span><span class="token punctuation">;</span>
</code></pre><h4 id="1116-模块划分">11.1.6 模块划分 </h4>
<p>另一种构建模块的方法是将它们拆分到单独的分区中。子模块和分区之间的区别是：子模块的构造对模块的用户来说是可见的，允许用户有选择地只导入想要使用的子模块。另一方面，分区用于在内部构造模块。分区不对模块的用户公开。在模块接口分区文件（module interface partition file）中声明的所有分区，最终必须又主模块接口文件导出。一个模块通常只有一个这样的主模块接口文件，那就是包含导出模块名称声明的接口文件。</p>
<p>模块分区名创建时使用冒号将模块名和分区名分隔开，分区名可以是任何合法的标识符。例如，前一节中的 DataMOdel 模块可以使用分区而不是子模块来重新构造。下面是在模块接口分区文件 datamodel.person.cppm 的 person 分区：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-export">export</span> <span class="token keyword keyword-module">module</span> <span class="token module">datamodel<span class="token operator">:</span>person</span><span class="token punctuation">;</span>

<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-namespace">namespace</span> DataModel <span class="token punctuation">{</span>
<span class="token keyword keyword-class">class</span> <span class="token class-name">Person</span>
<span class="token punctuation">{</span> <span class="token comment">/* ... */</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>   <span class="token comment">// namespace DataModel</span>
</code></pre><p>下面是包含默认构造函数的 address 分区：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-export">export</span> <span class="token keyword keyword-module">module</span> <span class="token module">datamodel<span class="token operator">:</span>address</span><span class="token punctuation">;</span>

<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-namespace">namespace</span> DataModel <span class="token punctuation">{</span>
<span class="token keyword keyword-class">class</span> <span class="token class-name">Address</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">Address</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">/* ... */</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>   <span class="token comment">// namespace DataModel</span>
</code></pre><p>但是，将实现文件与分区结合使用时需要注意：只有一个具特定分区名称的文件。因此，以下声明开头的实现文件的格式是不正确的。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-module">module</span> <span class="token module">datamodel<span class="token operator">:</span>address</span><span class="token punctuation">;</span>
</code></pre><p>相反，可以将 address 分区的实现放在 datamodel 模块的实现文件中，示例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">ifdef</span> <span class="token expression">WRITE_IN_VSCODE</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;iostream&gt;</span></span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">"datamodel.ixx"</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token keyword keyword-module">module</span> <span class="token module">datamodel</span><span class="token punctuation">;</span>
<span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;iostream&gt;</span></span><span class="token punctuation">;</span>
<span class="token keyword keyword-using">using</span> <span class="token keyword keyword-namespace">namespace</span> std<span class="token punctuation">;</span>
DataModel<span class="token double-colon punctuation">::</span><span class="token class-name">Address</span><span class="token double-colon punctuation">::</span><span class="token function">Address</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Address::Address()"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>多个文件不能具有相同多的分区名称。因此，具有多个相同分区名的模块接口文件是非法的，并且对于模块接口分区文件中声明的实现，也不能出现在具有相同分区名的实现文件中。相反，只需要将这些实现放在模块的实现文件中即可。</p>
<p>需要记住的重点是，在编写分区结构中的模块时，每个模块接口的分区最终都必须由主模块接口文件直接或间接导出。要导入分区，只需要指定（以冒号作为分隔）分区名称，比如 import:person。而 import datamodel:person 这样的则是非法的。记住，分区不会对模块的用户公开。分区只能在内部构造模块。因此，用户不能导入特定的分区；只能导入整个模块。分区只能在模块内部导入，因此在冒号之前指定模块名称是多余的（也是非法的）。下面是 datamodel 模块的主模块接口文件：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-export">export</span> <span class="token keyword keyword-module">module</span> <span class="token module">datamodel</span><span class="token punctuation">;</span>
<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-import">import</span> <span class="token module"><span class="token operator">:</span>person</span><span class="token punctuation">;</span>
<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-import">import</span> <span class="token module"><span class="token operator">:</span>address</span><span class="token punctuation">;</span>

<span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;vector&gt;</span></span><span class="token punctuation">;</span>

<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-namespace">namespace</span> DataModel <span class="token punctuation">{</span>
<span class="token keyword keyword-using">using</span> Persons <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span>vector<span class="token operator">&lt;</span>Person<span class="token operator">&gt;</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>这个分区结构的 datamodel 模块可以按以下方式使用：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-import">import</span> <span class="token module">datamodel</span><span class="token punctuation">;</span>
<span class="token keyword keyword-int">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    DataModel<span class="token double-colon punctuation">::</span>Address a<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><blockquote>
<p><strong>注意：</strong></p>
<p>分区用于在内部构造模块。分区在模块外部不可见。因此，模块的用户无法导入特定的分区；只能导入整个模块。</p>
</blockquote>
<p>前面已经解释过模块名称声明中隐式包括导入名称声明，但分区不是这样做的。</p>
<p>比如 datamodel:person 分区没有隐式导入 datamodel 的声明。在本例中，甚至不允许在 datamodel:person 接口文件中显式导入 datamodel。因为这样做将导致循环依赖：datamodel 接口文件中包含 import:person 的声明，而 datamodel:person 接口分区文件中包含 import datamodel 声明。</p>
<p>要打破这种循环依赖关系，可以将 datamodel:person 分区需要的功能从 datamodel 接口文件中转移至另一个分区，随后通过 datamodel:person 接口分区文件和 datamodel 接口文件导入该分区。</p>
<h5 id="实现分区">实现分区 </h5>
<p>分区不需要在模块的接口分区文件中声明，但可以在模块的实现分区文件（扩展名为 .cpp 的常规源码文件）中声明。这种情况下，它是实现分区，有时也称为内部分区。主模块的接口文件不会导出这样的分区。</p>
<p>例如，假设具有以下 math 主模块的接口文件（math.cppm）</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-export">export</span> <span class="token keyword keyword-module">module</span> <span class="token module">math</span><span class="token punctuation">;</span>
<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-namespace">namespace</span> Math <span class="token punctuation">{</span>
<span class="token keyword keyword-double">double</span> <span class="token function">superLog</span><span class="token punctuation">(</span><span class="token keyword keyword-double">double</span> z<span class="token punctuation">,</span> <span class="token keyword keyword-double">double</span> b<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword keyword-double">double</span> <span class="token function">lerchZeta</span><span class="token punctuation">(</span><span class="token keyword keyword-double">double</span> lambda<span class="token punctuation">,</span> <span class="token keyword keyword-double">double</span> alpha<span class="token punctuation">,</span> <span class="token keyword keyword-double">double</span> s<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>   <span class="token comment">// namespace Math</span>
</code></pre><p>进一步假设，math 函数的实现需要一些辅助函数，这些辅助函数不能被模块导出。实现分区是放置此类辅助函数的理想场所。下面在名为 ath_helpers.cpp 的文件中定义了这类实现分区：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-module">module</span> <span class="token module">math<span class="token operator">:</span>details</span><span class="token punctuation">;</span>
<span class="token keyword keyword-double">double</span> <span class="token function">someHelperFunction</span><span class="token punctuation">(</span><span class="token keyword keyword-double">double</span> a<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> <span class="token comment">/*  */</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>其他的 math 模块实现文件可以通过导入此类实现分区来访问这些辅助函数。比如，math 模块的实现文件（math.cpp）可能如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-module">module</span> <span class="token module">math</span><span class="token punctuation">;</span>
<span class="token keyword keyword-import">import</span> <span class="token module"><span class="token operator">:</span>details</span><span class="token punctuation">;</span>
<span class="token keyword keyword-double">double</span> <span class="token class-name">Math</span><span class="token double-colon punctuation">::</span><span class="token function">superLog</span><span class="token punctuation">(</span><span class="token keyword keyword-double">double</span> z<span class="token punctuation">,</span> <span class="token keyword keyword-double">double</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> <span class="token comment">/* ... */</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword keyword-double">double</span> <span class="token class-name">Math</span><span class="token double-colon punctuation">::</span><span class="token function">lerchZeta</span><span class="token punctuation">(</span><span class="token keyword keyword-double">double</span> lambda<span class="token punctuation">,</span> <span class="token keyword keyword-double">double</span> alpha<span class="token punctuation">,</span> <span class="token keyword keyword-double">double</span> s<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> <span class="token comment">/*  */</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>当然，只有当多个其他源文件都使用这些辅助函数的情况下，将实现分区与辅助函数一起使用才会有效果。</p>
<h4 id="1117-头文件单元">11.1.7 头文件单元 </h4>
<p>导入模块时，可以使用导入声明，示例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-import">import</span> <span class="token module">person</span><span class="token punctuation">;</span>
</code></pre><p>如果有遗留代码，比如定义 Person 类的 person.h 头文件，则可以通过将其转换为适当的模块（person.cppm）进行模块化，并导入声明使其对用户代码可用。但是，有时不能模块化这类头文件。也许对于还不支持模块的编译器，Person 类应该保持可用。或者 person.h 头文件是不能修改的第三方库的一部分。在这种情况下，可以如下所示，直接导入头文件：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">"person.h"</span></span>
</code></pre><p>有了这样的声明，person.h 头文件中的所有内容都将隐式导出。此外，头文件中定义的宏对于用户代码可见，但实际模块中却并非如此。</p>
<p>相比 #include 头文件，导入头文件可以提高构建效率，因为 person.h 头文件将隐式转换为模块，因此只需要编译一次，而不是在源文件中 #include 头文件时每次都进行编译。因此，它可以作为支持所谓的预编译头文件的标准化方式，而不是使用依赖于编译器的预编译头文件的支持。</p>
<p>对于每个命名头文件的导入声明，编译器必须创建一个模块，该模块具有类似于头文件定义的导入接口。这称为头文件单元。此过程取决于编译器，因此请参阅编译器的文档，了解如何使用头文件单元。</p>
<h3 id="112-头文件">11.2 头文件 </h3>
<p>在介绍 C++20 的模块之前，头文件被用作一种为子系统或代码段提供接口的机制。第 1 章简要介绍了头文件。基本上，头文件最常见的用法时用于声明在其他地方定义的函数。函数声明告诉编译器如何调用函数，声明形参的数量和类型，以及函数的返回类型。定义则包含该函数的实际代码。在 C++ 中，声明通常放入头文件中，扩展名为 .h；而定义通常放入源文件中，扩展名为 .cpp。本书在任何地方都将使用 C++20 的模块，但本节将会简要讨论使用头文件时一些复杂的情况，比如避免重复定义和循环依赖，因为在系统一六代码中可能会遇到这些问题。</p>
<h4 id="1121-重复定义">11.21 重复定义 </h4>
<p>假设 A.h 包含 Logger.h 头文件，Logger.h 头文件定义了 Logger 类；B.h 也包含了 Logger.h。如果具有一个名为 App.cpp 的源文件，其中同时包含 A.h 和 B.h，将会得到 Logger 类的重复定义，因为 Logger.h 头文件包含在 A.h 和 B.h 中。</p>
<p>这种重复定义的问题可以通过一种称为包含保护（也称为头文件保护）的机制来避免。下面的代码段显示了具有包含保护的 Logger.h 头文件。在每个头文件的开头，#ifndef 指令检查是否有某个标识符没有被定义。如果标识符已定义，那么编译器将会跳转至匹配的 #endif （通常将其放置在文件的末尾）。如果没有定义标识符，将会继续定义该标识符，以便跳过同一文件的后续包含。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">ifndef</span> <span class="token expression">LOGGER_H</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">LOGGER_H</span></span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Logger</span>
<span class="token punctuation">{</span> <span class="token comment">/* ... */</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>
</code></pre><p>另外，现在几乎所有的编译器都支持 <em><strong>#pragma once</strong></em> 指令，它取代了 include 保护指令。在头文件的开头放置 #pragma once，可以确保它只会被导入一次，从而避免了由于多次包含头而导致的重复定义。示例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">pragma</span> <span class="token expression">once</span></span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Logger</span>
<span class="token punctuation">{</span> <span class="token comment">/* ... */</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><h4 id="1122-循环依赖">11.2.2 循环依赖 </h4>
<p>避免头文件问题的另一个工具是<strong>前向声明</strong>（forward declaration）。如果需要引用一个类，但不能包含它的头文件（比如，它严重依赖于正在编写的类），则可以（通过前向声明）告诉编译器该类存在，而不需要通过 #include 提供正式的定义。当然，不能在代码中实际使用该类，因为编译器对它还一无所知，只知道已命名的类将所有内容链接在一起后存在于内存中。但仍然可以在代码中，通过指针和引用来引用前向声明的类。还可以声明按值返回的此类前向声明的类，或者声明一个函数，该函数的参数按值传递，类型为前向声明的类。当然，对于定义了函数的代码和调用该函数的任何代码，都需要包含正确定义了前向声明的类的头文件。</p>
<p>比如，假设 Logger 类使用另一个名为 Preferences 的类来跟踪用户的设置。Preferences 类可能反过来使用 Logger 类，因此会具有循环依赖关系，而且不能通过包含保护来解决。在这种情况下，需要使用前向声明。在下面的代码中，Logger.h 头文件使用了 Preferences 类的前向声明，因此可以在不包含其头文件的情况下，引用 Preferences 类。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">pragma</span> <span class="token expression">once</span></span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;string_view&gt;</span></span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Preferences</span><span class="token punctuation">;</span>

<span class="token keyword keyword-class">class</span> <span class="token class-name">Logger</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-void">void</span> <span class="token function">setPreferences</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Preferences<span class="token operator">&amp;</span> prefs<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-void">void</span> <span class="token function">logError</span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span>string_view error<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>建议尽可能在头文件中使用前向声明，而不是包含其他头文件。这么做可以减少编译和重新百衲衣的时间，因为它打破了当前头文件对其他头文件的依赖。当然，实现文件还是需要包含正确的头文件，即已前向声明的类型头文件；否则，它将无法通过编译。</p>
<h4 id="1123-查询头文件是否存在">11.2.3 查询头文件是否存在 </h4>
<p>若要查询某个头文件是否存在，请使用 <em><strong>__has_include("filename)</strong></em> 或 <em><strong>__has_include(&lt;filename&gt;)</strong></em> 预处理常量表达式。如果头文件已经存在，则返回值为1，否则为 0。例如，在 C++17 完全批准使用 &lt;optional&gt; 头文件之前，某些编译器已经在 &lt;experimental/optional&gt; 中具有初步版本。可以使用 __has_include() 检查这两个头文件中那一个系统中可用：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">if</span> <span class="token expression"><span class="token function">__has_include</span><span class="token punctuation">(</span><span class="token operator">&lt;</span>optional<span class="token operator">&gt;</span><span class="token punctuation">)</span></span></span>
    <span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;optional&gt;</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">elif</span> <span class="token expression"><span class="token function">__has_include</span><span class="token punctuation">(</span><span class="token operator">&lt;</span>experimental<span class="token operator">/</span>optional<span class="token operator">&gt;</span><span class="token punctuation">)</span></span></span>
    <span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;experimental/optional&gt;</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>
</code></pre><h3 id="113-核心语言特性的特性测试宏">11.3 核心语言特性的特性测试宏 </h3>
<p>跳过。</p>
<h3 id="114-static-关键字">11.4 static 关键字 </h3>
<p>在 C++ 中，static 关键字有多种用法，这些用法之间好像没有关系。“重载”关键字的部分原因是为了避免在语言中引入新的关键字。</p>
<h4 id="1141-静态数据成员和方法">11.4.1 静态数据成员和方法 </h4>
<p>可以声明类的静态数据成员和方法。与非静态数据成员不同，静态数据成员不是对象的一部分。相反，这个数据成员只有一个副本，这个副本存在于类的任何对象之外。</p>
<p>静态方法与此类似，位于类层次（而不是对象层次）。静态方法不会在某个特定对象的上下文中执行；因此，它没有隐式的 this 指针。但这也意味着静态方法不能被标记为 const。<br>
可见前第 9 章。</p>
<h4 id="1142-静态链接">11.4.2 静态链接 </h4>
<p>在介绍用于链接的 static 关键字之前，首先需要理解 C++ 中的链接概念。C++ 的每个源文件都是单独编译的，编译得到的目标文件会彼此链接。C++ 源文件中的每个名称（包括函数和全局变量）都具有外部或内部的链接。外部链接意味着这个名称在其他源文件中也有效。内部链接（也称为静态链接）意味着在其他源文件中无效。默认情况下，函数和全局变量都具有外部链接，但是，可以在声明前面加上 static 关键字来指定内部（或静态）链接。比如，假设有两个源文件：FirstFile.cpp 和 AnotherFile.cpp。下面的代码是 FirstFile.cpp：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-void">void</span> <span class="token function">f</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword keyword-int">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token function">f</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>注意，这个文件提供了函数 f() 的原型，但没有显式给出定义。下面的代码是 AnotherFile.cpp：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;iostream&gt;</span></span><span class="token punctuation">;</span>

<span class="token keyword keyword-void">void</span> <span class="token function">f</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword keyword-void">void</span> <span class="token function">f</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    std<span class="token double-colon punctuation">::</span>cout <span class="token operator">&lt;&lt;</span> <span class="token string">"f\n"</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>这个文件提供函数 f() 的原型和定义。注意，在两个不同的文件中为同一个函数编写原型是合法的。如果将原型放在头文件中，并且每个源文件中都用 #include 包含这个头文件，那么预处理器就会自动在每个源文件中给出函数原型。在下面的示例中，不使用头文件。过去使用头文件的原因是，它是更易于维护（并保持同步）原型的一个副本，但是从 C++20 开始，使用模块时建议不要使用头文件。</p>
<p>这些源文件中都可以成功编译，并且程序链接也没有问题：因为 f() 具有外部链接，main() 可以从其他文件调用它。</p>
<p>但是，假设对 AnotherFile.cpp 中的函数 f() 的原型应用了 static 关键字。注意，不需要在函数 f() 的定义前重复 static 关键字。只要它位于函数名称的第一个实例之前，就不需要重复它。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;iostream&gt;</span></span><span class="token punctuation">;</span>

<span class="token keyword keyword-static">static</span> <span class="token keyword keyword-void">void</span> <span class="token function">f</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword keyword-void">void</span> <span class="token function">f</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    std<span class="token double-colon punctuation">::</span>cout <span class="token operator">&lt;&lt;</span> <span class="token string">"f\n"</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>现在，每个源文件都可以正确编译，但是链接时却失败了，这是因为函数 f() 具有内部（静态）链接，从而是的 FirstFile.cpp 中无法使用这个函数。如果在源文件中定义了静态方法但是没有使用它，有些编译器会给出警告（指出这些方法不应该是静态的，因为其他文件可能会用到它们）。</p>
<p>将 static 用于内部链接的另一种方式是使用匿名的名称空间。将变量或函数封装到一个没有名字的名称空间中，而不是使用 static 关键字，示例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;iostream&gt;</span></span><span class="token punctuation">;</span>

<span class="token keyword keyword-namespace">namespace</span> <span class="token punctuation">{</span>
<span class="token keyword keyword-void">void</span> <span class="token function">f</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword keyword-void">void</span> <span class="token function">f</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    std<span class="token double-colon punctuation">::</span>cout <span class="token operator">&lt;&lt;</span> <span class="token string">"f\n"</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>   <span class="token comment">// namespace</span>
</code></pre><p>在同一源文件中，可在声明匿名名称空间之后的任何位置访问名称空间中的项，但不能在其他源文件中访问。这一语义与 static 关键字相同。</p>
<blockquote>
<p><strong>警告：</strong></p>
<p>要获得内部链接，建议使用匿名的名称空间，而不要使用 static 关键字。</p>
</blockquote>
<p><strong>extern 关键字</strong>.</p>
<p>extern 关键字好像是 static 关键字的反义词，将它后面的名称指定为外部链接，在某些情况下可以使用这种方法。例如，默认情况下，const 和 typedef 具有内部链接，可以使用 extern 使其变为外部链接。但是 extern 有一点复杂。当指定某个名称为 extern 时，编译器会将这条语句<strong>当作声明，而不是定义</strong>。对于变量，这意味着编译器<strong>不会为其分配空间</strong>。必须为这个变量提供单独的，而不是使用 extern 关键字的定义行。例如，下面是 AnotherFile.cpp 中的内容：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-extern">extern</span> <span class="token keyword keyword-int">int</span> x<span class="token punctuation">;</span>
<span class="token keyword keyword-int">int</span>        x<span class="token punctuation">{</span><span class="token number">3</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>或者，可以在 extern 语句中初始化 x，然后将其作为声明和定义。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-extern">extern</span> <span class="token keyword keyword-int">int</span> x<span class="token punctuation">{</span><span class="token number">3</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>在这种情况下，extern 不是非常有用，因为默认情况下，x 具有外部链接。当另一个源文件 FirstFile.cpp 使用 x 时，才会真正使用到 extern：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;iostream&gt;</span></span><span class="token punctuation">;</span>

<span class="token keyword keyword-extern">extern</span> <span class="token keyword keyword-int">int</span> x<span class="token punctuation">;</span>

<span class="token keyword keyword-int">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    std<span class="token double-colon punctuation">::</span>cout <span class="token operator">&lt;&lt;</span> x <span class="token operator">&lt;&lt;</span> std<span class="token double-colon punctuation">::</span>endl<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>本例中 FirstFile.cpp 使用了 extern 声明，因此可以使用 x。编译器需要知道 x 的声明之后，才能在 main() 中使用它。然而，如果在声明 x 时没有使用 extern 关键字，编译器会认为这是一个定义，并为 x 分配空间，从而导致链接失败（因为有两个全局作用域的 x 变量）。使用 extern 关键字，就可以在多个源文件中全局访问这个变量。</p>
<blockquote>
<p><strong>警告：</strong></p>
<p><strong>不建议使用全局变量</strong>，全局变量令人困惑且容易出错，尤其是在大型程序中。请明智地使用它们！</p>
</blockquote>
<h4 id="1143-函数中的静态变量">11.4.3 函数中的静态变量 </h4>
<p>在 C++ 中，static 关键字的最终目的是创建离开和进入作用域时都可保留值的局部变量。函数中的静态变量就像只能在函数内部访问的全部变量。静态变量最常见的用法是“记住”某个函数是否执行了特定的初始化操作。比如，下面代码就是用了这一技术：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-void">void</span> <span class="token function">performTask</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-static">static</span> <span class="token keyword keyword-bool">bool</span> initialized<span class="token punctuation">{</span><span class="token boolean">false</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>initialized<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        cout <span class="token operator">&lt;&lt;</span> <span class="token string">"initializing"</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
        initialized <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>但是，静态变量容易让人迷惑，在构建代码时通常有更好的方法，以避免使用静态变量。在这种情况下，可以编写一个类，用构造函数执行所需的初始化操作。</p>
<blockquote>
<p><strong>注意：</strong></p>
<p><strong>避免使用单独的静态变量，为了维持状态可以改用对象</strong>。</p>
</blockquote>
<p>但有时，它们十分有用。一个示例是实现 Meyer 的单例设计模式，详见第33 章。</p>
<blockquote>
<p><strong>注意：</strong><br>
performTask() 的实现不是线程安全的；它包含一个竟态条件。在多线程环境中，需要使用原子或其他机制来同步多个线程。关于线程的详细讨论在第 27 章。</p>
</blockquote>
<h4 id="1144-非局部变量的初始化顺序">11.4.4 非局部变量的初始化顺序 </h4>
<p>在结束静态数据成员和全局变量的主题之前，请考虑这些变量的初始化顺序。在 main() 开始之前，将初始化程序中的所有全局变量和静态类数据成员。对于给定源文件中的变量，按照它们在源文件中出现的顺序进行初始化。例如，在下面的示例中，Demo::x 一定会在 y 之前初始化。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">Demo</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-static">static</span> <span class="token keyword keyword-int">int</span> x<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-int">int</span> Demo<span class="token double-colon punctuation">::</span>x<span class="token punctuation">{</span><span class="token number">3</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-int">int</span> y<span class="token punctuation">{</span><span class="token number">4</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>然而，C++ 没有提供规范，用以说明在不同源文件中初始化非局部变量的顺序。如果在某个源文件中有一个全局变量，在另一个源文件中有一个全局变量 y，无法知道哪个全局变量将首先初始化。通常，不需要关注这一规范的缺失，但如果某个全局变量或静态变量依赖于另一个变量，就可能引发问题。对象的初始化意味着调用构造函数，全局对象的构造函数可能会访问另一个全局对象，并假定另一个全局对象已经构建。如果这两个全局对象在不同的源文件中声明，就不能指望一个全局对象在零个全局对象之前构建，也无法控制它们的初始化顺序。不同编译器可能有不同的初始化顺序，即使同一编译器的不同版本也可能如此，甚至在项目中添加另一源文件也可能影响初始化顺序。</p>
<blockquote>
<p><strong>警告：</strong></p>
<p>在不同源文件中，非局部变量的初始化顺序是不确定的。</p>
</blockquote>
<h4 id="1145-非局部变量的销毁顺序">11.4.5 非局部变量的销毁顺序 </h4>
<p>非局部变量的销毁顺序与其初始化顺序相反。对于不同源文件中的非局部变量，初始化的顺序是不确定的，这意味着其销毁的顺序也是不确定的。</p>
<h3 id="115-c-的实用工具">11.5 C 的实用工具 </h3>
<p>一些晦涩的 C 特性在 C++ 中也可以用。本节研究其中的两个特性：变长参数列表（variable-length-argument lists）和预处理器宏（preprocessor macros）。</p>
<h4 id="1151-边长参数列表">11.5.1 边长参数列表 </h4>
<p>本节将介绍旧的 C 风格的边长参数列表。需要知道它们是如何工作的，因为可能会在遗留代码中看到它们。然而，在新的代码中，应该通过变参模板使用类型安全的边长参数列表，变参模板将在第 26 章“高级模板”中进行介绍。对于 &lt;cstdio&gt; 中的 C 函数 printf()，可以使用任意数量的参数来调用它。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"int %d\n"</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"String %s and int %d\n"</span><span class="token punctuation">,</span> <span class="token string">"hello"</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"Many ints: %d, %d, %d, %d, %d\n"</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>C/C++ 提供了语法和一些实用宏，用于编写具有可变数量的函数。这些函数通常看起来很像 printf()，尽管并非经常需要，但偶尔需要这个特性。例如，假设想编写一个快速调试的函数，如果设置了调试标记，这个函数向 stderr 输出字符串，但如果没有设置调试标记，就什么都不做。于 printf() 一样，这个函数应该能接收任意数目和任意类型的参数并输出字符串。这个函数的简单实现如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;cstdarg&gt;</span></span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">&lt;cstdio&gt;</span></span>

<span class="token keyword keyword-bool">bool</span> debug<span class="token punctuation">{</span><span class="token boolean">false</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-void">void</span> <span class="token function">debugOut</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> <span class="token keyword keyword-char">char</span><span class="token operator">*</span> str<span class="token punctuation">,</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    va_list ap<span class="token punctuation">;</span>
    <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>debug<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token function">va_start</span><span class="token punctuation">(</span>ap<span class="token punctuation">,</span> str<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">vfprintf</span><span class="token punctuation">(</span><span class="token constant">stderr</span><span class="token punctuation">,</span> str<span class="token punctuation">,</span> ap<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">va_end</span><span class="token punctuation">(</span>ap<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>首先，注意 debugOut() 函数的原型包含一个具有类型和名称的参数 str，之后是省略号（…），这代表任意数目和类型的参数。如果要访问这些参数，必须使用&lt;cstdarg&gt; 中定义的宏。声明一个 va_list 类型的变量，并调用 va_start() 对其进行初始化。va_start() 的第二个参数必须是参数类表中最右边的已命名变量。所有具有变长参数列表的函数都至少需要一个已命名的参数。debugOut() 函数只是将列表传递给 vfprintf()（&lt;cstdio&gt; 中的标准函数）。vfprintf() 的调用返回时，debugOut() 调用 va_end() 来终止对变长参数列表的访问。在调用 va_start() 之后，必须总是调用 va_end()，以确保函数结束后，堆栈处于稳定的状态。</p>
<p>函数 debugOut() 的用法如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>debug <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
<span class="token function">debugOut</span><span class="token punctuation">(</span><span class="token string">"int %d\n"</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">debugOut</span><span class="token punctuation">(</span><span class="token string">"String %s and int %d\n"</span><span class="token punctuation">,</span> <span class="token string">"hello"</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">debugOut</span><span class="token punctuation">(</span><span class="token string">"Many ints: %d, %d, %d, %d, %d\n"</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><h5 id="访问参数">访问参数 </h5>
<p>如果想要访问实际参数，那么可以使用 va_arg()；它接收 va_list 作为第一个参数，以及需要解析的参数的类型作为第二参数。但是，如果不提供显示的方法，就无法知道参数列表的结尾是什么。例如，可以将第一个参数作为参数个数的计数。或者，当参数是一组指针时，可以要求最后一个指针是 nullptr。方法很多，但对于程序员来说，所有方法都很麻烦。</p>
<p>下面的示例演示了这种技术，其中调用者在第一个已命名参数中指定了所提供参数的数目。函数接收任意数目的 int 参数，并将其输出。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-void">void</span> <span class="token function">printInts</span><span class="token punctuation">(</span>size_t num<span class="token punctuation">,</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    va_list ap<span class="token punctuation">;</span>
    <span class="token function">va_start</span><span class="token punctuation">(</span>ap<span class="token punctuation">,</span> num<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-for">for</span> <span class="token punctuation">(</span>size_t i<span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">}</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> num<span class="token punctuation">;</span> <span class="token operator">++</span>i<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-int">int</span> temp<span class="token punctuation">{</span><span class="token function">va_arg</span><span class="token punctuation">(</span>ap<span class="token punctuation">,</span> <span class="token keyword keyword-int">int</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
        cout <span class="token operator">&lt;&lt;</span> temp <span class="token operator">&lt;&lt;</span> <span class="token string">" "</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token function">va_end</span><span class="token punctuation">(</span>ap<span class="token punctuation">)</span><span class="token punctuation">;</span>
    cout <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>可以按以下方式调用 printInts()。注意，第一个参数将指定后面整数的数目。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token function">printInts</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><h5 id="为什么不应该使用-c-风格的边长参数列表">为什么不应该使用 C 风格的边长参数列表 </h5>
<p>访问 C 风格的变长参数列表并不十分安全。从 printInts() 函数可以看出，这种方法存在以下风险：</p>
<ul>
<li><strong>不知道参数的数目。</strong> 在 printInts() 中，必须信任调用者传递了与第一个参数指定的数目相等的参数。在 debugOut() 中，必须相信调用者在字符数组之后传递的参数数目与字符数组中的格式代码一致。</li>
<li><strong>不知道参数的类型。</strong> va_arg() 接收一种类型，用来解释当前的值，然而，可让 va_arg() 将这个值解释为任意类型，无法验证正确的类型。</li>
</ul>
<blockquote>
<p><strong>警告：</strong></p>
<p>避免使用 C 风格的变长参数列表。使用第 1 章介绍的初始化列表时，最好传入 std::array 或者值矢量；对于类型安全的变长参数列表，可以使用变参模板，这一主题将在第 22 章讲述。</p>
</blockquote>
<h4 id="1152-预处理器宏">11.5.2 预处理器宏 </h4>
<p>可使用 C++ 预处理器宏，这与函数有点类似。下面是一个示例：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name function">SQUARE</span><span class="token expression"><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">)</span></span></span>

<span class="token keyword keyword-int">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    cout <span class="token operator">&lt;&lt;</span> <span class="token function">SQUARE</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
    <span class="token keyword keyword-return">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>宏是 C 遗留下来的特性，非常类似于内联函数，但不执行类型检测。在调用宏时，预处理器会自动使用扩展式替换。预处理器并不会真正地应用函数调用语义，这一行为可能导致无法预测的结果。例如，如果用 2+3 而不是 5 调用 SQUARE 宏，考虑一下会发生什么，如下所示。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>cout <span class="token operator">&lt;&lt;</span> <span class="token function">SQUARE</span><span class="token punctuation">(</span><span class="token number">2</span> <span class="token operator">+</span> <span class="token number">3</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
</code></pre><p>SQUARE 宏的计算结果应为 25，结果确实如此。然而，如果在宏定义中省略部分圆括号，这个宏看上去是这样的：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name function">SQUARE</span><span class="token expression"><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token punctuation">(</span>x <span class="token operator">*</span> x<span class="token punctuation">)</span></span></span>
</code></pre><p>现在，调用 SQUARE(2 + 3) 的结果是 11，而不是 25！注意宏只是自动扩展，而不考虑函数调用语义。这意味着在宏中，x 被替换为 2+3，扩展式是：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>cout <span class="token operator">&lt;&lt;</span> <span class="token function">SQUARE</span><span class="token punctuation">(</span><span class="token number">2</span> <span class="token operator">+</span> <span class="token number">3</span> <span class="token operator">*</span> <span class="token number">2</span> <span class="token operator">+</span> <span class="token number">3</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
</code></pre><p>按照正确的操作顺序，这行代码首先执行乘法，然后是加法，结果是 11 而不是 25。<br>
宏还会影响性能，假定按如下方式调用 SQUARE 宏：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>cout <span class="token operator">&lt;&lt;</span> <span class="token function">SQUARE</span><span class="token punctuation">(</span><span class="token function">veryExpensiveFunctionCallToComputeNumber</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
</code></pre><p>预处理把它替换为：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>cout <span class="token operator">&lt;&lt;</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token function">veryExpensiveFunctionCallToComputeNumber</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">*</span>
         <span class="token punctuation">(</span><span class="token function">veryExpensiveFunctionCallToComputeNumber</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
     <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>
</code></pre><p>现在，这个开销很大的函数调用了两次。这是避免使用宏的另一个原因。</p>
<p>宏还会导致调试问题，因为编写的代码并非编译器看到的代码或者调试工具中显示的代码（因为预处理的查找和替换功能）。为此，<strong>应该全部用内联函数替换宏</strong>。在这里详细讲述宏，只是因为相当多的 C++ 代码使用了 宏，为了阅读并维护这些代码，应该理解宏。</p>
<blockquote>
<p><strong>注意：</strong></p>
<p>大多数编译器可将经过预处理器处理的源代码输出到某个文件。使用这个文件可以观察预处理器是如何处理文件的。例如，使用 MSVC 时需要使用 /P 选项，使用 GCC 时，可以使用 -E 选项。</p>
</blockquote>
<h2 id="12-利用模板编写泛型代码">12 利用模板编写泛型代码 </h2>
<p>C++ 不仅支持面向对象编程，还支持泛型编程（generic programming）。根据第 6 章“设计可重用代码”的讨论，泛型编程的目的时编写可重用的代码。在 C++ 中，泛型编程的基本工具是模板。尽管从严格意义上说，模板并不是一个面向对象的特性，但模板可与面向对象编程结合使用，从而产生强大的作用。</p>
<p>本章列出了满足第 6 章讨论的一般性设计原则所需的编码细节，第 22 章“高级模板”将讨论一些高级模板特性。</p>
<h3 id="121-模板概述">12.1 模板概述 </h3>
<p>面向过程编程泛型中主要的编程单元式过程或函数。主要使用的是函数，因为函数可用于编写不依赖特定值的算法，从而可重用很多不同的值。例如，C++ 中的 sqrt() 函数计算调用者指定的值的平方跟。只能计算一个数字（例如 4）的平方根的函数没有什么实际用途，sqrt() 函数是基于参数编写的，参数实际上是一个占位符，可表示调用者传入的任何数值。用计算机科学家的话来说，就是用函数参数化值。</p>
<p>面向对象编程范式中添加了对象的概念，对象将相关的数据和行为组织起来，但它并没有改变函数和方法参数化值的方式。</p>
<p>模板将参数化的概念推进了一步，不仅允许参数化值，还允许参数化类型。C++ 中的类型不仅包括基本类型，例如 int 和 double，还包括用户定义的类，例如 SpreadsheetCell 和 CherryTree。使用模板，不仅可编写不依赖特定值的代码，还能编写不依赖那些值类型的代码。例如，不需要为保存int、Car 和 SpreadsheetCell 而编写不同的堆栈类，但可以编写一个堆栈的类定义，这个类定义可用于任何类型。</p>
<p>尽管模板是一个令人惊叹的语言特性，但由于 C++ 模板的语法令人费解，很多程序员会选择忽略或避免使用模板。不过，每个程序员至少需要知道如何使用模板，因为程序库（例如 C++ 标准库）中广泛使用了模板。</p>
<p>这一章讲解 C++ 中的模板支持，重点讲述模板在标准库中的使用。在学习过程中，可以学会一些有用的细微特性，除了标准库之外，还可以在程序中使用这些特性。</p>
<h3 id="122-类模板">12.2 类模板 </h3>
<p>类模板定义了一个类，其中，将一些变量的类型、方法和返回类型和/或方法的参数类型指定为参数。类模板主要用于容器，或用于保存对象的数据结构。本节使用一个 Grid 容器作为示例。为了让这些例子长度合理，且足够简单地演示特定的知识点，本章不同的小节会向 Grid 容器添加一些特性，这些特性在接下来的几个章节将不会被用到，</p>
<h4 id="1221-编写类模板">12.2.1 编写类模板 </h4>
<p>假设想要一个通用的棋盘类，可将其用作对象棋棋盘、跳棋棋盘、井字棋棋盘或其他任何二维的棋盘。为让这个棋盘通用，这个棋盘应该能保存象棋棋子、跳棋棋子、井字棋棋子或其他任何游戏类型的棋子。</p>
<h5 id="编写不使用模板的代码">编写不使用模板的代码 </h5>
<p>如果不使用模板，编写通用棋盘最好的方法是采用多态技术，保存通用的 GamePiece 对象，然后，可让每种游戏的棋子继承 GamePiece 类。例如，在象棋游戏中，ChessPiece 可以是 GamePiece 的派生类，通过多态技术，既能保存 GamePiece 的 GameBoard，也能保存 ChessPiece。GameBoard 可以复制，所以 GameBoard 需要能复制 GamePiece。这个实现利用了多态技术，因此一种解决方案是给 GamePiece 基类添加纯虚方法 clone()，它的派生类必须实现 clone()，并返回一个具体的 GamePiece 的副本。GamePiece 基类如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-export">export</span> <span class="token keyword keyword-class">class</span> <span class="token class-name">GamePiece</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">GamePiece</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                             <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-virtual">virtual</span> std<span class="token double-colon punctuation">::</span>unique_ptr<span class="token operator">&lt;</span>GamePiece<span class="token operator">&gt;</span> <span class="token function">clone</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>GamePiece 是一个抽象基类。ChessPiece 等具体类派生于它，并实现了 clone() 方法。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">ChessPiece</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">GamePiece</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    std<span class="token double-colon punctuation">::</span>unique_ptr<span class="token operator">&lt;</span>GamePiece<span class="token operator">&gt;</span> <span class="token function">clone</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token keyword keyword-override">override</span>
    <span class="token punctuation">{</span>
        <span class="token keyword keyword-return">return</span> std<span class="token double-colon punctuation">::</span><span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span>ChessPiece<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token operator">*</span><span class="token keyword keyword-this">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>GameBoard 的实现使用 unique_ptr 矢量组中的矢量来存储 GamePiece。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-export">export</span> <span class="token keyword keyword-class">class</span> <span class="token class-name">GameBoard</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-explicit">explicit</span> <span class="token function">GameBoard</span><span class="token punctuation">(</span>size_t width <span class="token operator">=</span> DefaultWidth<span class="token punctuation">,</span> size_t height <span class="token operator">=</span> DefaultHeight<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">GameBoard</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> GameBoard<span class="token operator">&amp;</span> src<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">GameBoard</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    GameBoard<span class="token operator">&amp;</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> GameBoard<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token function">GameBoard</span><span class="token punctuation">(</span>GameBoard<span class="token operator">&amp;&amp;</span> src<span class="token punctuation">)</span>            <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    GameBoard<span class="token operator">&amp;</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span>GameBoard<span class="token operator">&amp;&amp;</span> src<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>

    std<span class="token double-colon punctuation">::</span>unique_ptr<span class="token operator">&lt;</span>GamePiece<span class="token operator">&gt;</span><span class="token operator">&amp;</span>       <span class="token function">at</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-const">const</span> std<span class="token double-colon punctuation">::</span>unique_ptr<span class="token operator">&lt;</span>GamePiece<span class="token operator">&gt;</span><span class="token operator">&amp;</span> <span class="token function">at</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>

    size_t <span class="token function">getWidth</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token punctuation">{</span> <span class="token keyword keyword-return">return</span> m_width<span class="token punctuation">;</span> <span class="token punctuation">}</span>
    size_t <span class="token function">getHeight</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token punctuation">{</span> <span class="token keyword keyword-return">return</span> m_height<span class="token punctuation">;</span> <span class="token punctuation">}</span>

    <span class="token keyword keyword-static">static</span> <span class="token keyword keyword-const">const</span> size_t DefaultWidth<span class="token punctuation">{</span><span class="token number">10</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-static">static</span> <span class="token keyword keyword-const">const</span> size_t DefaultHeight<span class="token punctuation">{</span><span class="token number">10</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token keyword keyword-void">void</span> <span class="token function">swap</span><span class="token punctuation">(</span>GameBoard<span class="token operator">&amp;</span> other<span class="token punctuation">)</span> <span class="token keyword keyword-noexcept">noexcept</span><span class="token punctuation">;</span>

<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    <span class="token keyword keyword-void">void</span> <span class="token function">verifyCoordinate</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>

    std<span class="token double-colon punctuation">::</span>vector<span class="token operator">&lt;</span>std<span class="token double-colon punctuation">::</span>vector<span class="token operator">&lt;</span>std<span class="token double-colon punctuation">::</span>unique_ptr<span class="token operator">&lt;</span>GamePiece<span class="token operator">&gt;&gt;</span><span class="token operator">&gt;</span> m_cells<span class="token punctuation">;</span>

    size_t m_width<span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">}</span><span class="token punctuation">,</span> m_height<span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>在这个实现中，at() 返回指定位置的棋子的引用，而不是返回棋子的副本。GameBoard 作为一个二维数组的抽象，它应给出实际对象的索引，而不是给出对象的副本，以提供数组访问语义。客户代码不应存储这个引用将来使用，因为它可能是无效的；相反，应该在使用返回的引用之前调用 at()。这遵循了标准库中 std::vector 类的设计原理。</p>
<blockquote>
<p><strong>注意：</strong></p>
<p>GameBoard 类的这个实现提供了 at() 的两个版本，一个版本返回引用，另一个版本返回 const 引用。</p>
</blockquote>
<p>下面是方法的定义。注意，这个方法为赋值运算符使用了“复制和交换”惯用语法，还使用 Scott Meyer 的 const_cast() 模式来避免代码重复，第 9 章讨论了这些主题。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-module">module</span> <span class="token module">game_board</span><span class="token punctuation">;</span>
    
<span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;format&gt;</span></span><span class="token punctuation">;</span>
<span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;stdexcept&gt;</span></span><span class="token punctuation">;</span>
<span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;utility&gt;</span></span><span class="token punctuation">;</span>
    
<span class="token keyword keyword-using">using</span> <span class="token keyword keyword-namespace">namespace</span> std<span class="token punctuation">;</span>
    
<span class="token class-name">GameBoard</span><span class="token double-colon punctuation">::</span><span class="token function">GameBoard</span><span class="token punctuation">(</span>size_t width<span class="token punctuation">,</span> size_t height<span class="token punctuation">)</span>
    <span class="token operator">:</span> m_width<span class="token punctuation">{</span>width<span class="token punctuation">}</span>
    <span class="token punctuation">,</span> m_height<span class="token punctuation">{</span>height<span class="token punctuation">}</span>
<span class="token punctuation">{</span>
    m_cells<span class="token punctuation">.</span><span class="token function">resize</span><span class="token punctuation">(</span>m_width<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-for">for</span> <span class="token punctuation">(</span><span class="token keyword keyword-auto">auto</span><span class="token operator">&amp;</span> column <span class="token operator">:</span> m_cells<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        column<span class="token punctuation">.</span><span class="token function">resize</span><span class="token punctuation">(</span>m_height<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
    
<span class="token class-name">GameBoard</span><span class="token double-colon punctuation">::</span><span class="token function">GameBoard</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> GameBoard<span class="token operator">&amp;</span> src<span class="token punctuation">)</span>
    <span class="token operator">:</span> GameBoard<span class="token punctuation">{</span>src<span class="token punctuation">.</span>m_width<span class="token punctuation">,</span> src<span class="token punctuation">.</span>m_height<span class="token punctuation">}</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-for">for</span> <span class="token punctuation">(</span>size_t i<span class="token punctuation">{</span><span class="token number">9</span><span class="token punctuation">}</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> m_width<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-for">for</span> <span class="token punctuation">(</span>size_t j<span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">}</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> m_height<span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>src<span class="token punctuation">.</span>m_cells<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                m_cells<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> src<span class="token punctuation">.</span>m_cells<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token operator">-&gt;</span><span class="token function">clone</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
    
<span class="token keyword keyword-void">void</span> <span class="token class-name">GameBoard</span><span class="token double-colon punctuation">::</span><span class="token function">verifyCoordinate</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>x <span class="token operator">&gt;=</span> m_width<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-throw">throw</span> std<span class="token double-colon punctuation">::</span>out_of_range<span class="token punctuation">{</span>std<span class="token double-colon punctuation">::</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"{} must be less than {}."</span><span class="token punctuation">,</span> x<span class="token punctuation">,</span> m_width<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>y <span class="token operator">&gt;=</span> m_height<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-throw">throw</span> std<span class="token double-colon punctuation">::</span>out_of_range<span class="token punctuation">{</span>std<span class="token double-colon punctuation">::</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"{} must be less than {}."</span><span class="token punctuation">,</span> y<span class="token punctuation">,</span> m_height<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
    
<span class="token keyword keyword-void">void</span> <span class="token class-name">GameBoard</span><span class="token double-colon punctuation">::</span><span class="token function">swap</span><span class="token punctuation">(</span>GameBoard<span class="token operator">&amp;</span> other<span class="token punctuation">)</span> <span class="token keyword keyword-noexcept">noexcept</span>
<span class="token punctuation">{</span>
    std<span class="token double-colon punctuation">::</span><span class="token function">swap</span><span class="token punctuation">(</span>m_width<span class="token punctuation">,</span> other<span class="token punctuation">.</span>m_width<span class="token punctuation">)</span><span class="token punctuation">;</span>
    std<span class="token double-colon punctuation">::</span><span class="token function">swap</span><span class="token punctuation">(</span>m_height<span class="token punctuation">,</span> other<span class="token punctuation">.</span>m_height<span class="token punctuation">)</span><span class="token punctuation">;</span>
    std<span class="token double-colon punctuation">::</span><span class="token function">swap</span><span class="token punctuation">(</span>m_cells<span class="token punctuation">,</span> other<span class="token punctuation">.</span>m_cells<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
    
<span class="token keyword keyword-void">void</span> <span class="token function">swap</span><span class="token punctuation">(</span>GameBoard<span class="token operator">&amp;</span> first<span class="token punctuation">,</span> GameBoard<span class="token operator">&amp;</span> second<span class="token punctuation">)</span> <span class="token keyword keyword-noexcept">noexcept</span>
<span class="token punctuation">{</span>
    first<span class="token punctuation">.</span><span class="token function">swap</span><span class="token punctuation">(</span>second<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
    
GameBoard<span class="token operator">&amp;</span> GameBoard<span class="token double-colon punctuation">::</span><span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> GameBoard<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    GameBoard temp<span class="token punctuation">{</span>rhs<span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token function">swap</span><span class="token punctuation">(</span>temp<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-return">return</span> <span class="token operator">*</span><span class="token keyword keyword-this">this</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
    
<span class="token keyword keyword-const">const</span> unique_ptr<span class="token operator">&lt;</span>GamePiece<span class="token operator">&gt;</span><span class="token operator">&amp;</span> <span class="token class-name">GameBoard</span><span class="token double-colon punctuation">::</span><span class="token function">at</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span>
<span class="token punctuation">{</span>
    <span class="token function">verifyCoordinate</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-return">return</span> m_cells<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">[</span>y<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
    
unique_ptr<span class="token operator">&lt;</span>GamePiece<span class="token operator">&gt;</span><span class="token operator">&amp;</span> <span class="token class-name">GameBoard</span><span class="token double-colon punctuation">::</span><span class="token function">at</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> <span class="token generic-function"><span class="token function">const_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span>unique_ptr<span class="token operator">&lt;</span>GamePiece<span class="token operator">&gt;</span><span class="token operator">&amp;</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token function">as_const</span><span class="token punctuation">(</span><span class="token operator">*</span><span class="token keyword keyword-this">this</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">at</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>这个 GameBoard 类可以很好地完成任务。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>GameBoard chessBoard<span class="token punctuation">{</span><span class="token number">8</span><span class="token punctuation">,</span> <span class="token number">8</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword keyword-auto">auto</span>      pawn<span class="token punctuation">{</span>std<span class="token double-colon punctuation">::</span><span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span>ChessPiece<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
chessBoard<span class="token punctuation">.</span><span class="token function">at</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span><span class="token function">move</span><span class="token punctuation">(</span>pawn<span class="token punctuation">)</span><span class="token punctuation">;</span>
chessBoard<span class="token punctuation">.</span><span class="token function">at</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span><span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span>ChessPiece<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
chessBoard<span class="token punctuation">.</span><span class="token function">at</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-nullptr">nullptr</span><span class="token punctuation">;</span>
</code></pre><h5 id="模板-grid-类">模板 Grid 类 </h5>
<p>前面定义的 GameBoard 类很好，但不够完善。第一个问题就是无法使用 GameBoard 按值存储元素，它总是存储指针。另一个更重要的问题与类型安全相关。GameBoard 中的每个网格都存储 unique_ptr&lt;GamePiece&gt;。即使存储 VhessPiece，当使用 at() 请求某个网格时，也会得到 unique_ptr&lt;GamePiece&gt;。这意味着，只有将检测到的 GamePiece 向下转型为 ChessPiece，才能使用 ChessPiece 的特定功能。GameBoard 的另一个缺点是不能用来存储基本类型，如 int 或 double，因为存储在网格中的类型必须派生自类 GamePiece。</p>
<p>因此，最好编写一个通用的 Grid 类，该类可用于存储 ChessPiece、SpreadsheetCell、int 和 double 等。在 C++ 中，可通过编写类模板来实现这一点，编写类模板可避免编写需要指定一种或多种类型的类。客户通过指定要使用的类型对模板进行实例化。这称为<strong>泛型编程</strong>，其最大的优点是类型安全。类及其方法中使用的类型是具体的类型，而不是多态方案中的抽象基本类型。例如，假设不仅有派生类 ChessPiece，还有派生类 TicTacToePiece。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-class">class</span> <span class="token class-name">TicTacToePiece</span> <span class="token operator">:</span> <span class="token base-clause"><span class="token keyword keyword-public">public</span> <span class="token class-name">GamePiece</span></span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    std<span class="token double-colon punctuation">::</span>unique_ptr<span class="token operator">&lt;</span>GamePiece<span class="token operator">&gt;</span> <span class="token function">clone</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token keyword keyword-override">override</span>
    <span class="token punctuation">{</span>
        <span class="token keyword keyword-return">return</span> std<span class="token double-colon punctuation">::</span><span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span>TicTacToePiece<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token operator">*</span><span class="token keyword keyword-this">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>使用前面介绍的多态解决方案，显然可以在同一棋盘中存储象棋棋子和井字棋棋子。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>GameBoard chessBoard<span class="token punctuation">{</span><span class="token number">8</span><span class="token punctuation">,</span> <span class="token number">8</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
chessBoard<span class="token punctuation">.</span><span class="token function">at</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span><span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span>ChessPiece<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
chessBoard<span class="token punctuation">.</span><span class="token function">at</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span><span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span>TicTacToePiece<span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>最大的问题在于，在一定程度上，只有记住网格中存储的内容，才能在调用 at() 时正确地向下转型。</p>
<h6 id="grid-类定义">Grid 类定义 </h6>
<p>为理解类模板，最好首先看一下类模板的语法。下例展示了如何修改 GameBoard 类，得到模板化的 Grid 类。代码之后会对所有语法进行解释。注意，类名从 GameBoard 变为 Grid。这个 Grid 类还应该可以用于基本类型，如 int 和 double。因此，这里选用不带多态性的值语义来实现这个解决方案，而 GameBoard 实现中使用了多态指针语义。m_cells 容器中存储的是真实的对象，而不是指针。与指针语义相比，使用值语义的缺点在于不能拥有真正的空网格。幸运的是，第 1 章介绍的 std::optional 可弥补这一点。它允许使用值语义，并且同时允许表示空网格。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-export">export</span> <span class="token keyword keyword-template">template</span><span class="token operator">&lt;</span><span class="token keyword keyword-typename">typename</span> <span class="token class-name">T</span><span class="token operator">&gt;</span> <span class="token keyword keyword-class">class</span> <span class="token class-name">Grid</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token keyword keyword-explicit">explicit</span> <span class="token function">Grid</span><span class="token punctuation">(</span>size_t width  <span class="token operator">=</span> DefaultWidth<span class="token punctuation">,</span>   <span class="token comment">//</span>
                  size_t height <span class="token operator">=</span> DefaultHeight<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">Grid</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>

    <span class="token function">Grid</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Grid<span class="token operator">&amp;</span> src<span class="token punctuation">)</span>            <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    Grid<span class="token operator">&amp;</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Grid<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>

    <span class="token function">Grid</span><span class="token punctuation">(</span>Grid<span class="token operator">&amp;&amp;</span> src<span class="token punctuation">)</span>            <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    Grid<span class="token operator">&amp;</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span>Grid<span class="token operator">&amp;&amp;</span> rhs<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>

    std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token operator">&amp;</span>       <span class="token function">at</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-const">const</span> std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token operator">&amp;</span> <span class="token function">at</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>

    size_t <span class="token function">getHeight</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token punctuation">{</span> <span class="token keyword keyword-return">return</span> m_height<span class="token punctuation">;</span> <span class="token punctuation">}</span>
    size_t <span class="token function">getWidth</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token punctuation">{</span> <span class="token keyword keyword-return">return</span> m_width<span class="token punctuation">;</span> <span class="token punctuation">}</span>

    <span class="token keyword keyword-static">static</span> <span class="token keyword keyword-const">const</span> size_t DefaultWidth<span class="token punctuation">{</span><span class="token number">10</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-static">static</span> <span class="token keyword keyword-const">const</span> size_t DefaultHeight<span class="token punctuation">{</span><span class="token number">10</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    <span class="token keyword keyword-void">void</span> <span class="token function">verifyCoordinate</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>

    std<span class="token double-colon punctuation">::</span>vector<span class="token operator">&lt;</span>std<span class="token double-colon punctuation">::</span>vector<span class="token operator">&lt;</span>std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span>T<span class="token operator">&gt;&gt;</span><span class="token operator">&gt;</span> m_cells<span class="token punctuation">;</span>

    size_t m_width<span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">}</span><span class="token punctuation">,</span> m_height<span class="token punctuation">{</span><span class="token number">0</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>现在已展示了完整的类定义，下面逐行分析这些代码：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-export">export</span> <span class="token keyword keyword-template">template</span> <span class="token operator">&lt;</span>typeneme T<span class="token operator">&gt;</span>
</code></pre><p>第一行表示，下面的类定义是基于一种类型（T）的模板，改类型从模块中导出。template 和 typename 都是 C++ 中关键字。如前所述，模板“参数化”类型的方式与函数“参数化”值的方式相同。就像在函数中通过参数名表示调用者传入的参数一样，在模板中使用模板类型参数名称（例如 T）表示调用者传入的作为模板类型实参的类型。名称 T 没有特别之处，可以使用任何名称。按照惯例，只使用一种类型时，将这种类型称为 T，但这只是一项历史约定，就像把索引数组的整数命名为 i 或 j 一样。这个模板说明符应用于整个语句，在这里是整个类定义。</p>
<blockquote>
<p><strong>注意：</strong></p>
<p>基于历史原因，指定模板类型参数时，可用关键字 class 替代 typename。因此，很多数据和现有的程序使用了这样的语法：template&lt;class T&gt;。不过，在这个上下文中使用 class 这个词会产生一些误解，因为这个词暗示这种这种类型必须是一个类，而实际上并不要求这样，这种类型可以是类、struct、union、语言的基本类型（例如 int 或 double 等）。</p>
</blockquote>
<p>在前面的 GameBoard 类中，m_cells 数据成员是指针的矢量组中的矢量，这需要特定的复制代码，因此需要复制构造函数和赋值运算符。在 Grids 类中，m_cells 是可选值的矢量组中的矢量，所以编译器生成的复制构造函数和赋值运算符可以运行得很好。但如第 8 章“属性类和对象”所述，一旦有了用户声明的析构函数，建议不要使用编译器隐式生成复制构造函数或赋值运算符，因此 Grif 类模板将其显示设置为 default，并且将移动构造函数和赋值运算符显式设置为 default。下面将复制赋值运算符显式设置为 default：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Grid<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token operator">&amp;</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Grid<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
</code></pre><p>从中可以看出，rhs 参数的类型不再是 cosnt GameBoard&amp; 了，而是 const Grid&amp;。在类定义中，编译器根据需要将 Grid 解释为 Grid&lt;T&gt;，但可以根据需要，显式地使用 Grid&lt;T&gt;。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Grid<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token operator">&amp;</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Grid<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
</code></pre><p>但在类定义之外，必须使用 Grid&lt;T&gt;。在编写类模板时，以前的类名（Grid）现在实际上是模板名称。讨论实际的 Grid 类或类型时，需要使用模板 ID（如：Grid&lt;T&gt;），它是 Grid 类模板对某种类型实例化的结果，例如 int、SpreadsheetCell 或 ChessPiece。</p>
<p>m_cells 不再存储指针，而是存储可选值。at() 方法现在返回 option&lt;T&gt;&amp;，而不是返回 unique_ptr。也就是说，optional 中可以包含 T 类型的值或者为空。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token operator">&amp;</span>       <span class="token function">at</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword keyword-const">const</span> std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token operator">&amp;</span> <span class="token function">at</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>
</code></pre><h6 id="grid-类的方法定义">Grid 类的方法定义 </h6>
<p>template &lt;typename T&gt; 访问符必须在 Grid 模板的每一个方法定义的前面。构造函数如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-template">template</span><span class="token operator">&lt;</span><span class="token keyword keyword-typename">typename</span> <span class="token class-name">T</span><span class="token operator">&gt;</span>
<span class="token class-name">Grid</span><span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token double-colon punctuation">::</span><span class="token function">Grid</span><span class="token punctuation">(</span>size_t width<span class="token punctuation">,</span> size_t height<span class="token punctuation">)</span>
    <span class="token operator">:</span> m_width<span class="token punctuation">{</span>width<span class="token punctuation">}</span>
    <span class="token punctuation">,</span> m_height<span class="token punctuation">{</span>height<span class="token punctuation">}</span>
<span class="token punctuation">{</span>
    m_cells<span class="token punctuation">.</span><span class="token function">resize</span><span class="token punctuation">(</span>m_width<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-for">for</span> <span class="token punctuation">(</span><span class="token keyword keyword-auto">auto</span><span class="token operator">&amp;</span> colum <span class="token operator">:</span> m_cells<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        colum<span class="token punctuation">.</span><span class="token function">resize</span><span class="token punctuation">(</span>m_height<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><blockquote>
<p><strong>注意：</strong></p>
<p>类模板中方法的定义需要对所有使用该模板的客户代码可见。这对放置方法定义的位置有一些限制。通常，它们这是与类模板定义本身放在相同的文件中。本章稍后将讨论绕过这个限制的一些方法。</p>
</blockquote>
<p>注意：之前的类名是 ::Grid&lt;T&gt;，而不是 Grid。必须在所有的方法和静态成员定义中将 Grid&lt;T&gt; 指定为类名。构造函数的函数体类似于 GameBoard 构造函数。</p>
<p>其他方法定义也类似于 GameBoard 类中对应的方法定义，只是适当地改变了模板和 Grid&lt;T&gt; 的语法：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-template">template</span><span class="token operator">&lt;</span><span class="token keyword keyword-typename">typename</span> <span class="token class-name">T</span><span class="token operator">&gt;</span>   <span class="token comment">//</span>
<span class="token keyword keyword-void">void</span> <span class="token class-name">Grid</span><span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token double-colon punctuation">::</span><span class="token function">verifyCoordinate</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>x <span class="token operator">&gt;=</span> m_width<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-throw">throw</span> std<span class="token double-colon punctuation">::</span>out_of_range   <span class="token comment">//</span>
            <span class="token punctuation">{</span>std<span class="token double-colon punctuation">::</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"{} must be less than {}."</span><span class="token punctuation">,</span> x<span class="token punctuation">,</span> m_width<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>y <span class="token operator">&gt;=</span> m_height<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-throw">throw</span> std<span class="token double-colon punctuation">::</span>out_of_range   <span class="token comment">//</span>
            <span class="token punctuation">{</span>std<span class="token double-colon punctuation">::</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"{} must be less than {}."</span><span class="token punctuation">,</span> y<span class="token punctuation">,</span> m_height<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword keyword-template">template</span><span class="token operator">&lt;</span><span class="token keyword keyword-typename">typename</span> <span class="token class-name">T</span><span class="token operator">&gt;</span>   <span class="token comment">//</span>
<span class="token keyword keyword-const">const</span> std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token operator">&amp;</span> <span class="token class-name">Grid</span><span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token double-colon punctuation">::</span><span class="token function">at</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span>
<span class="token punctuation">{</span>
    <span class="token function">verifyCoordinate</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-return">return</span> m_cells<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">[</span>y<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword keyword-template">template</span><span class="token operator">&lt;</span><span class="token keyword keyword-typename">typename</span> <span class="token class-name">T</span><span class="token operator">&gt;</span>   <span class="token comment">//</span>
std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token operator">&amp;</span> <span class="token class-name">Grid</span><span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token double-colon punctuation">::</span><span class="token function">at</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> <span class="token generic-function"><span class="token function">const_cast</span><span class="token generic class-name"><span class="token operator">&lt;</span>std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token operator">&amp;</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span><span class="token function">as_const</span><span class="token punctuation">(</span><span class="token operator">*</span><span class="token keyword keyword-this">this</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">at</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><blockquote>
<p><strong>注意：</strong></p>
<p>如果类模板方法的实现需要特定模板类型参数（例如 T）的默认值。可使用 T{} 语法。如果 T 是类类型，T{} 调用对象的默认构造函数、或者如果 T 是基本类型，则生成 0.这称为“初始化为 0”语法。最好为类型尚未确定的变量提供合理的默认值。</p>
</blockquote>
<h5 id="使用-grid-模板">使用 Grid 模板 </h5>
<p>创建网格对象时，不能单独使用 Grid 作为类型：必须指定这个网格保存的元素类型。为某种类型创建一个模板类对象的过程称为模板的实例化。下面举一个示例：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Grid<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span> myIntGrid<span class="token punctuation">;</span>

Grid<span class="token operator">&lt;</span><span class="token keyword keyword-double">double</span><span class="token operator">&gt;</span> myDoubleGrid<span class="token punctuation">{</span><span class="token number">11</span><span class="token punctuation">,</span> <span class="token number">11</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

myIntGrid<span class="token punctuation">.</span><span class="token function">at</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span>
<span class="token keyword keyword-int">int</span> x<span class="token punctuation">{</span>myIntGrid<span class="token punctuation">.</span><span class="token function">at</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">value_or</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

Grid<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span> grid2<span class="token punctuation">{</span>myIntGrid<span class="token punctuation">}</span><span class="token punctuation">;</span>
Grid<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span> anotherIntGrid<span class="token punctuation">;</span>
anotherIntGrid <span class="token operator">=</span> grid2<span class="token punctuation">;</span>
</code></pre><p>注意 myIntGrid、grid2 和 anotherGrid 的类型为 Grid&lt;int&gt;。不能将 SpreadsheetCell 或 ChessPiece 保存在这些网格中，否则编译器会生成错误消息。</p>
<p>另外，这里使用了 value_or()。at() 方法返回 std::optional 引用。Optional 可以包含值，也可以不包含值。如果 optional 包含值，value_or() 方法返回这个值；否则返回给 value_or() 提供的实参。</p>
<p>类型规范非常重要，下面两行代码都无法编译：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Grid test<span class="token punctuation">;</span>
Grid<span class="token operator">&lt;</span><span class="token operator">&gt;</span> test<span class="token punctuation">;</span>
</code></pre><p>编译器对第一行代码会给出如下错误：“使用类模板要求提供模板参数列表。”编译器对第二行代码会给出如下错误：“模板参数太少。”</p>
<p>如果要声明一个接收 Grid 对象的函数或方法，必须在 Grid 类型中指定保存在网格中的元素类型。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-void">void</span> <span class="token function">processIntGrid</span><span class="token punctuation">(</span>Grid<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span><span class="token operator">&amp;</span> grid<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
   <span class="token string">"以下省略"</span>
<span class="token punctuation">}</span>
</code></pre><p>另外，可使用将在本章后面介绍的函数模板，基于网格中的元素类型编写函数模板。</p>
<blockquote>
<p><strong>注意：</strong></p>
<p>为避免每次都编写完整的 Grid 类型名称，例如 Grid&lt;int&gt;，可通过类型别名指定一个更简单的名称。</p>
<p><code>using IntGrid = Grid\&lt;int&gt;;</code></p>
<p>现在可编写以下代码：</p>
<p><code>void processIntGrid(IntGrid&amp; grid) { }</code></p>
</blockquote>
<p>Grid 类模板能保存的数据类型不只是 int。例如，可实例化一个保存 SpreadsheetCell 的网格：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Grid<span class="token operator">&lt;</span>SpreadsheetCell<span class="token operator">&gt;</span> mySpreadsheet<span class="token punctuation">;</span>
SpreadsheetCell       myCell<span class="token punctuation">{</span><span class="token number">1.234</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
mySpreadsheet<span class="token punctuation">.</span><span class="token function">at</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">)</span> <span class="token operator">=</span> myCell<span class="token punctuation">;</span>
</code></pre><p>还可以保存指针类型：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Grid<span class="token operator">&lt;</span><span class="token keyword keyword-const">const</span> <span class="token keyword keyword-char">char</span><span class="token operator">*</span><span class="token operator">&gt;</span> myStringGrid<span class="token punctuation">;</span>
myStringGrid<span class="token punctuation">.</span><span class="token function">at</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"hello"</span><span class="token punctuation">;</span>
</code></pre><p>指定的类型甚至可以是另一个模板类型：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Grid<span class="token operator">&lt;</span>vector<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;&gt;</span> gridOfVectors<span class="token punctuation">;</span>
vector<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span>       myVector<span class="token punctuation">{</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
gridOfVectors<span class="token punctuation">.</span><span class="token function">at</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">)</span> <span class="token operator">=</span> myVector<span class="token punctuation">;</span>
</code></pre><p>还可在自由存储区（堆）上动态分配 Grid 模板实例：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-auto">auto</span> myGridOnHeap<span class="token punctuation">{</span><span class="token generic-function"><span class="token function">make_unique</span><span class="token generic class-name"><span class="token operator">&lt;</span>Grid<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;&gt;</span></span></span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
myGridOnHeap<span class="token operator">-&gt;</span><span class="token function">at</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span>
<span class="token keyword keyword-int">int</span> x<span class="token punctuation">{</span>myGridOnHEap<span class="token operator">-&gt;</span><span class="token function">at</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">value_or</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><h4 id="1222-编译器处理模板的原理">12.2.2 编译器处理模板的原理 </h4>
<p>为理解模板的复杂性，必须学习编译器处理模板代码的原理。编译器遇到模板方法定义时，会进行语法检查，但是并不编译模板。编译器无法编译模板定义，因为它不知道要使用什么类型。如果不知道 x 和 y 的类型，那么编译器就无法为 x = y 这样的语句生成代码。</p>
<p>编译器遇到一个实例化的模板时，例如 Grid&lt;int&gt; ，就会将模板类定义中的每一个 T 替换为 int，从而生成 Grid 模板的 int 版本代码。当编译器遇到这个模板的另一个实例时，例如 Grid&lt;SpreadsheetCell&gt; ，就会为 SpreadsheetCell 生成另一个版本的 Grid 类。编译器生成代码的方式就好像语言不支持模板时程序员编写代码的方式：为每种元素类型编写一个不同的类。这里没有什么神奇之处，模板只是自动完成了一个令人厌烦的过程。如果在程序中没有将类模板实例化为任何类型，就不会编译类方法的定义。</p>
<p>这个实例化的过程页解释了为什么需要在定义中的多个地方使用 Grid&lt;T&gt; 语法。当编译器为某种特定类型实例化模板时，例如 int，就将 T 替换为 int，变成 Grid&lt;int&gt;类型。</p>
<h5 id="1-选择性实例化">1. 选择性实例化 </h5>
<p>类模板的隐式实例化如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Grid<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span> myIntGrid<span class="token punctuation">;</span>
</code></pre><p>编译器总是为类模板的所有虚方法生成代码。但对于非虚方法，编译器只会为那些实际为某种类型调用的非虚方法生成代码。例如，给定前面定义的 Grid 类模板，假设在 main() 中编写这段代码（而且只有这段代码）：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Grid<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span> myIntGrid<span class="token punctuation">;</span>
myIntGrid<span class="token punctuation">.</span><span class="token function">at</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span>
</code></pre><p>编译器只会为 int 版本的 Grid 类生成无参构造函数、析构函数和非常量 at() 方法的代码。它不会为其他方法生成代码，例如复制构造函数、赋值运算符或 getHeight()。编译器的以上行为成为<strong>选择性实例化</strong>（selective instantiation）。</p>
<p>存在这样的危险：在一些类模板方法中存在编译错误，而这些错误会被忽略。类模板中未使用的方法可能包含语法错误，因为这些错误不会被编译。这使得很难检测所有代码中的语法错误。通过显式的模板实例化，可以强制编译器为所有的方法生成代码，包括虚方法和非虚方法。示例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-template">template</span> <span class="token keyword keyword-class">class</span> <span class="token class-name">Grid</span><span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span><span class="token punctuation">;</span>
</code></pre><blockquote>
<p><strong>注意：</strong></p>
<p>显式的模板实例化有助于发现错误，因为它会强制编译所有的类模板方法，甚至在未使用的时候也是如此。</p>
</blockquote>
<p>当使用显式的模板实例化时，不要只尝试使用基本类型（如 int）来实例化类模板，而应该尝试使用更复杂的类型（如 string）来实例化类模板。</p>
<h5 id="2-模板对类型的要求">2. 模板对类型的要求 </h5>
<p>在编写与类型无关的代码时，必须对这些类型有一些假设。例如，在 Grid 类模板中，假设元素类型（用 T 表示）是可析构的、可复制/移动构造的，可复制/移动赋值的。</p>
<p>如果在程序中试图用一种不支持模板使用的所有操作的类型对模板进行实例化，那么这段代码无法编译，而且错误消息几乎总是晦涩难懂。然而，就算要使用的类型不支持所有模板代码所需的操作，也仍然可以利用选择性实例化使用某些方法，而避免使用另一些方法。</p>
<p>c++20 引入了<strong>概念</strong>（concepts），允许编写编译器可以解释和验证的模板参数需求。如果传递给实例化模板的模板实参不满足这些要求，编译器会生成更多可读的错误。相关概念将在本章后面讨论。</p>
<h4 id="1223-将模板代码分布到多个文件中">12.2.3 将模板代码分布到多个文件中 </h4>
<p>对于类模板，编译器必须可以从使用它们的任何源文件中获取类模板定义和方法定义。有几种机制可以实现这一点。</p>
<h5 id="1-将方法的定义与模板的定义放在相同文件中">1. 将方法的定义与模板的定义放在相同文件中 </h5>
<p>可以将方法的定义直接放在定义类模板的相同模块接口文件中。当使用模板的另一个源文件中导入此模块时，编译器将能访问所需的所有代码。这个机制可以用于之前的 Grid 实现。</p>
<h5 id="2-将方法的定义放在单独的文件中">2. 将方法的定义放在单独的文件中 </h5>
<p>或者，可以将类模板方法的定义放在单独的模块接口分区文件中。然后，还需要将类模板的定义放在它自己的分区中。例如，Grid 类模板的基本模块接口文件看起来像下面这样</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-export">export</span> <span class="token keyword keyword-import">import</span> <span class="token module">grid</span><span class="token punctuation">;</span>

<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-import">import</span> <span class="token module"><span class="token operator">:</span>definition</span><span class="token punctuation">;</span>
<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-import">import</span> <span class="token module"><span class="token operator">:</span>implementation</span><span class="token punctuation">;</span>
</code></pre><p>这里导入和导出两个模块分区：definition 和 implementation。类模板定义在 definition 分区中：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-export">export</span> <span class="token keyword keyword-module">module</span> <span class="token module">grid<span class="token operator">:</span>definition</span><span class="token punctuation">;</span>

<span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;vector&gt;</span></span><span class="token punctuation">;</span>
<span class="token keyword keyword-import">import</span> <span class="token module"><span class="token string">&lt;optional&gt;</span></span><span class="token punctuation">;</span>

<span class="token keyword keyword-export">export</span> <span class="token keyword keyword-template">template</span><span class="token operator">&lt;</span><span class="token keyword keyword-typename">typename</span> <span class="token class-name">T</span><span class="token operator">&gt;</span> <span class="token keyword keyword-class">class</span> <span class="token class-name">Grid</span>
<span class="token punctuation">{</span>
    <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>如果方法的实现在 implement 分区中，那么 implement 分区也需要导入 definition 分区，因为它需要 Grid 类模板定义：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-export">export</span> <span class="token keyword keyword-template">template</span><span class="token operator">&lt;</span><span class="token keyword keyword-typename">typename</span> <span class="token class-name">T</span><span class="token operator">&gt;</span>
<span class="token class-name">Grid</span><span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token double-colon punctuation">::</span><span class="token function">Grid</span><span class="token punctuation">(</span>size_t width<span class="token punctuation">,</span> size_t height<span class="token punctuation">)</span>
    <span class="token operator">:</span> m_width<span class="token punctuation">{</span>width<span class="token punctuation">}</span>
    <span class="token punctuation">,</span> m_height<span class="token punctuation">{</span>height<span class="token punctuation">}</span> <span class="token punctuation">{</span>
          <span class="token comment">// ...</span>
      <span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><h4 id="1224-模板参数">12.2.4 模板参数 </h4>
<p>在 Grid 示例中，Grid 模板包含一个模板参数：保存在网格中的元素的类型。在编写这个类模板时，需要在尖括号中指定参数列表，示例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-template">template</span><span class="token operator">&lt;</span><span class="token keyword keyword-typename">typename</span> <span class="token class-name">T</span><span class="token operator">&gt;</span>
</code></pre><p>这个参数列表类似于函数或方法中的参数列表。与函数或方法一样，可使用任意多个模板参数来编写类。此外，这些参数可以不是类型，并且可以具有默认值。</p>
<h5 id="1-非类型的模板参数">1. 非类型的模板参数 </h5>
<p>非类型的模板参数是“普通”参数，例如 int 和指针：函数和方法中你十分熟悉的那种参数。然而，非类型的模板参数只能是整数类型（char、int、long 等）、枚举类型、指针、引用、std::nullptr_t、auto、auto&amp; 和 auto*。此外，从 c++20 开始，可允许浮点型（甚至类类型）的非类型的模板参数。然而，后者又很多限制，本文不再进一步讨论。</p>
<p>在 Grid 类模板中，可通过非类型模板参数指定网格的高度和宽度，而不是在构造函数中指定它们。在模板列表中指定非类型参数而不是在构造函数中指定的主要好处是：在编译代码之前就已经知道这些参数的值了。前面提到，编译器为模板化的方法生成代码的方式是通过在编译之前替换模板参数。因此，在这个实现中，可使用普通的二维数组而不是动态分配大小的矢量组中的矢量。下面是突出显示的新的类定义：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-export">export</span> <span class="token keyword keyword-template">template</span><span class="token operator">&lt;</span><span class="token keyword keyword-typename">typename</span> <span class="token class-name">T</span><span class="token punctuation">,</span> size_t WIDTH<span class="token punctuation">,</span> size_t HEIGHT<span class="token operator">&gt;</span> <span class="token keyword keyword-class">class</span> <span class="token class-name">Grid</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">Grid</span><span class="token punctuation">(</span><span class="token punctuation">)</span>          <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-virtual">virtual</span> <span class="token operator">~</span><span class="token function">Grid</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>

    <span class="token function">Grid</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Grid<span class="token operator">&amp;</span> src<span class="token punctuation">)</span>            <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>
    Grid<span class="token operator">&amp;</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Grid<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword keyword-default">default</span><span class="token punctuation">;</span>

    std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token operator">&amp;</span>       <span class="token function">at</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-const">const</span> std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token operator">&amp;</span> <span class="token function">at</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>

    size_t <span class="token function">getHeight</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token punctuation">{</span> <span class="token keyword keyword-return">return</span> HEIGHT<span class="token punctuation">;</span> <span class="token punctuation">}</span>
    size_t <span class="token function">getWidth</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token punctuation">{</span> <span class="token keyword keyword-return">return</span> WIDTH<span class="token punctuation">;</span> <span class="token punctuation">}</span>

<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    <span class="token keyword keyword-void">void</span> <span class="token function">verifyCoordinate</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span><span class="token punctuation">;</span>

    std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span> m_cells<span class="token punctuation">[</span>WIDTH<span class="token punctuation">]</span><span class="token punctuation">[</span>HEIGHT<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>这个类没有显式地将移动构造函数和移动赋值运算符设置为默认，原因是 C 风格的数组不支持移动语义。可以将实现对移动语义的支持作为练习。</p>
<p>注意，模板参数列表需要 3 个参数：网格中保存的对象类型以及网格的宽度和高度。宽度和高度用于创建保存对象的二维数组。下面是类方法定义：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-template">template</span><span class="token operator">&lt;</span><span class="token keyword keyword-typename">typename</span> <span class="token class-name">T</span><span class="token punctuation">,</span> size_t WIDTH<span class="token punctuation">,</span> size_t HEIGHT<span class="token operator">&gt;</span>
<span class="token keyword keyword-void">void</span> <span class="token class-name">Grid</span><span class="token operator">&lt;</span>T<span class="token punctuation">,</span> WIDTH<span class="token punctuation">,</span> HEIGHT<span class="token operator">&gt;</span><span class="token double-colon punctuation">::</span><span class="token function">verifyCoordinate</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>x <span class="token operator">&gt;=</span> WIDTH<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-throw">throw</span> std<span class="token double-colon punctuation">::</span>out_of_range   <span class="token comment">//</span>
            <span class="token punctuation">{</span>std<span class="token double-colon punctuation">::</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"{} must be less than {}."</span><span class="token punctuation">,</span> x<span class="token punctuation">,</span> WIDTH<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>x <span class="token operator">&gt;=</span> WIDTH<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword keyword-throw">throw</span> std<span class="token double-colon punctuation">::</span>out_of_range   <span class="token comment">//</span>
            <span class="token punctuation">{</span>std<span class="token double-colon punctuation">::</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"{} must be less than {}."</span><span class="token punctuation">,</span> x<span class="token punctuation">,</span> WIDTH<span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword keyword-template">template</span><span class="token operator">&lt;</span><span class="token keyword keyword-typename">typename</span> <span class="token class-name">T</span><span class="token punctuation">,</span> size_t WIDTH<span class="token punctuation">,</span> size_t HEIGHT<span class="token operator">&gt;</span>
<span class="token keyword keyword-const">const</span> std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token operator">&amp;</span> Grid<span class="token operator">&lt;</span>T<span class="token punctuation">,</span> WIDTH<span class="token punctuation">,</span> HEIGHT<span class="token operator">&gt;</span><span class="token double-colon punctuation">::</span>   <span class="token comment">//</span>
    <span class="token function">at</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span>
<span class="token punctuation">{</span>
    <span class="token function">verifyCoordinate</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-return">return</span> m_cells<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">[</span>y<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword keyword-template">template</span><span class="token operator">&lt;</span><span class="token keyword keyword-typename">typename</span> <span class="token class-name">T</span><span class="token punctuation">,</span> size_t WIDTH<span class="token punctuation">,</span> size_t HEIGHT<span class="token operator">&gt;</span>
std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token operator">&amp;</span> Grid<span class="token operator">&lt;</span>T<span class="token punctuation">,</span> WIDTH<span class="token punctuation">,</span> HEIGHT<span class="token operator">&gt;</span><span class="token double-colon punctuation">::</span>   <span class="token comment">//</span>
    <span class="token function">at</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> <span class="token keyword keyword-const_cast">const_cast</span><span class="token operator">&lt;</span>std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token operator">&amp;</span><span class="token operator">&gt;</span>   <span class="token comment">//</span>
        <span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span><span class="token function">as_const</span><span class="token punctuation">(</span><span class="token operator">*</span><span class="token keyword keyword-this">this</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">at</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>注意之前所有指定 Grid&lt;T&gt; 的地方，现在都必须指定 Grid&lt;T, WIDTH, HEIGHT&gt; 来表示这 3 个模板参数。</p>
<p>可通过以下方式实例化这个模板：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Grid<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token operator">&gt;</span> myGrid<span class="token punctuation">;</span>
Grid<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token operator">&gt;</span> anotherGrid<span class="token punctuation">;</span>
myGrid<span class="token punctuation">.</span><span class="token function">at</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token number">42</span><span class="token punctuation">;</span>
anotherGrid     <span class="token operator">=</span> myGrid<span class="token punctuation">;</span>
cout <span class="token operator">&lt;&lt;</span> anotherGrid<span class="token punctuation">.</span><span class="token function">at</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">value_or</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>这段代码看上去很棒。但遗憾的是，实际中的限制比想象中的要多。首先，不能通过非常量的整数指定高度或宽度。下面的代码无法编译：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>size_t height<span class="token punctuation">{</span><span class="token number">10</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

Grid<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">,</span> height<span class="token operator">&gt;</span> testGrid<span class="token punctuation">;</span>   <span class="token comment">// 无法编译</span>
</code></pre><p>然而，如果把 height 声明为 const，这段代码可以编译通过。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-const">const</span> size_t height<span class="token punctuation">{</span><span class="token number">10</span><span class="token punctuation">}</span><span class="token punctuation">;</span>

Grid<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">,</span> height<span class="token operator">&gt;</span> testGrid<span class="token punctuation">;</span>   <span class="token comment">// 编译通过</span>
</code></pre><p>带有正确返回类型的 constexpr 函数也可以编译。例如，如果有一个返回 size_t 的 constexpr 函数，那么可以使用它初始化 height 模板参数。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-constexpr">constexpr</span> size_t <span class="token function">getHeight</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword keyword-return">return</span> <span class="token number">10</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

Grid<span class="token operator">&lt;</span><span class="token keyword keyword-double">double</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token function">getHeight</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">&gt;</span> myDoubleGrid<span class="token punctuation">;</span>
</code></pre><p>另一个限制可能更明显。既然宽度和高度都是模板参数，那么它们也是每种网格类型的一部分。这意味着 Grid&lt;int,10,10&gt; 和 Grid&lt;int,10,11&gt; 是两种类型。不能将一种类型的对象赋给另一种类型的对象，而且一种类型的变量不能传递给接收另一种类型的变量的函数或方法。</p>
<blockquote>
<p><strong>注意：</strong></p>
<p>非类型模板参数是实例化的对象的类型规范中的一部分。</p>
</blockquote>
<h5 id="2-类型参数的默认值">2. 类型参数的默认值 </h5>
<p>如果继续采用将高度和宽度作为模板参数的方式，就可能需要为高度和宽度（它们是非类型模板参数）提供默认值，就像之前 Grid&lt;T&gt; 类的构造函数一样。C++ 允许使用类似的语法向模板参数提供默认值。在这里也可以给 T 类型参数提供默认值。下面是类定义：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-export">export</span> <span class="token keyword keyword-template">template</span><span class="token operator">&lt;</span><span class="token keyword keyword-typename">typename</span> <span class="token class-name">T</span> <span class="token operator">=</span> <span class="token keyword keyword-int">int</span><span class="token punctuation">,</span> size_t WIDTH <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">,</span> size_t HEIGHT <span class="token operator">=</span> <span class="token number">10</span><span class="token operator">&gt;</span>   <span class="token comment">//</span>
<span class="token keyword keyword-class">class</span> <span class="token class-name">Grid</span>
<span class="token punctuation">{</span>
    <span class="token comment">//...</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>不需要在方法定义的模板规范中指定 T、WIDTH 和 HEIGHT 的默认值。例如，下面是 at() 方法的实现：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-template">template</span><span class="token operator">&lt;</span><span class="token keyword keyword-typename">typename</span> <span class="token class-name">T</span><span class="token punctuation">,</span> size_t WIDTH<span class="token punctuation">,</span> size_t HEIGHT<span class="token operator">&gt;</span>   <span class="token comment">//</span>
<span class="token keyword keyword-const">const</span> std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token operator">&amp;</span> <span class="token class-name">Grid</span><span class="token operator">&lt;</span>T<span class="token punctuation">,</span> WIDTH<span class="token punctuation">,</span> HEIGHT<span class="token operator">&gt;</span><span class="token double-colon punctuation">::</span><span class="token function">at</span><span class="token punctuation">(</span>size_t x<span class="token punctuation">,</span> size_t y<span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span>
<span class="token punctuation">{</span>
    <span class="token function">verifyCoordinate</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword keyword-return">return</span> m_cells<span class="token punctuation">[</span>x<span class="token punctuation">]</span><span class="token punctuation">[</span>y<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>现在，实例化 Grid 时，可以不指定模板参数，只指定元素类型，或者指定元素类型和高度，或者指定元素类型、宽度和高度。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Grid<span class="token operator">&lt;</span><span class="token operator">&gt;</span>          myIntGrid<span class="token punctuation">;</span>
Grid<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span>       myGrid<span class="token punctuation">;</span>
Grid<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token operator">&gt;</span>    anotherGrid<span class="token punctuation">;</span>
Grid<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token operator">&gt;</span> aFourthGrid<span class="token punctuation">;</span>
</code></pre><p>注意，如果未指定任何类模板参数，那么仍需要指定一组空尖括号。例如，以下代码无法编译：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Grid myIntGrid<span class="token punctuation">;</span>
</code></pre><p>模板参数列表中默认的规则与函数或方法是一样的。可以从右向左提供参数的默认值。</p>
<h5 id="3-类模板的实参推导">3. 类模板的实参推导 </h5>
<p>通过类模板实参推导，编译器可以自动从传递给类模板构造函数的实参推导出模板参数。</p>
<p>例如，标准库有一个名为 std::pair 的类模板，定义在 &lt;utility&gt; 中，在前面第 1 章中介绍过。一个 pair 存储两种不同类型的两个值，必须将其指定为模板参数。示例如下：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>pair<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token punctuation">,</span> <span class="token keyword keyword-double">double</span><span class="token operator">&gt;</span> pair1<span class="token punctuation">{</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2.3</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>为避免编写模板参数，可以使用一个辅助的函数模板 <strong>std::make_pair()</strong>。本章后面将详细讨论函数模板。函数模板始终支持基于传递给函数模板的实参自动推导模板参数。因此，make_pair() 能根据传递给它的值自动推导模板类型参数。例如，编译器为以下调用推导出 pair&lt;int,double&gt;：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-auto">auto</span> pair2<span class="token punctuation">{</span><span class="token function">make_pair</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2.3</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>使用类模板实参推导（CTAD），这样的辅助函数模板就不再需要了。现在，编译器可以根据传递给构造函数的实参自动推导模板类型参数。对于 pair 类模板，只需要编写以下代码：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>pair pair3<span class="token punctuation">{</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2.3</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>当然，推导的前提是类模板的所有模板参数要么有默认值，要么用作构造函数中的参数。</p>
<p>注意，CTAD 需要一个初始化式才能工作。以下是非法的：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>pair pair4<span class="token punctuation">;</span>
</code></pre><p>许多标准库类都支持 CTAD，例如，向量，数组等。</p>
<blockquote>
<p><strong>注意：</strong></p>
<p>std::unique_ptr 和 share_ptr 会禁用类型推导。给它们的构造函数传递 T*，这意味着，编译器必须选择推导 &lt;T&gt; 还是 &lt;T[]&gt;，这是一个可能出错的危险选择。因此只需要记住，<mark>对于 unique_ptr 和 share_ptr，需要继续使用 make_unique() 和 make_shared()。</mark></p>
</blockquote>
<h5 id="用户定义的推导规则">用户定义的推导规则 </h5>
<p>可以编写自己的推导原则（即用户定义的推导原则）来帮助编译器。它允许编写如何推导模板参数的规则。这是一个高级主题，这里不对其进行详细讨论，但会举一个例子来演示其功能。</p>
<p>假设具有以下 SpreadsheetCell 类模板：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token keyword keyword-template">template</span><span class="token operator">&lt;</span><span class="token keyword keyword-typename">typename</span> <span class="token class-name">T</span><span class="token operator">&gt;</span> <span class="token keyword keyword-class">class</span> <span class="token class-name">SpreadsheetCell</span>
<span class="token punctuation">{</span>
<span class="token keyword keyword-public">public</span><span class="token operator">:</span>
    <span class="token function">SpreadsheetCell</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> T<span class="token operator">&amp;</span> t<span class="token punctuation">)</span>
        <span class="token operator">:</span> <span class="token function">m_content</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span>
    <span class="token punctuation">{</span><span class="token punctuation">}</span>
    <span class="token keyword keyword-const">const</span> T<span class="token operator">&amp;</span> <span class="token function">getContent</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword keyword-const">const</span> <span class="token punctuation">{</span> <span class="token keyword keyword-return">return</span> m_content<span class="token punctuation">;</span> <span class="token punctuation">}</span>

<span class="token keyword keyword-private">private</span><span class="token operator">:</span>
    T m_content<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>通过自动推导模板参数，可使用 std::string 类型创建 SpreadsheetCell：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>string          myString<span class="token punctuation">{</span><span class="token string">"Hello World!"</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
SpreadsheetCell cell<span class="token punctuation">{</span>myString<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>但是，如果给 SpreadsheetCell 构造函数传递 const char*，那么会将类型 T 推导为 const char*，这不是需要的结果。可以创建以下用户定义的推导原则，在将 const char* 作为实参传递给构造函数时，将 T 推导为 std::string。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token function">SpreadsheetCell</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> <span class="token keyword keyword-char">char</span><span class="token operator">*</span><span class="token punctuation">)</span> <span class="token operator">-&gt;</span> SpreadsheetCell<span class="token operator">&lt;</span>std<span class="token double-colon punctuation">::</span>string<span class="token operator">&gt;</span><span class="token punctuation">;</span>
</code></pre><p>在与 SpreadsheetCell 类相同的名称空间中，在类定义之外定义该原则。</p>
<p>通用语法如下。explicit 关键字是可选的，它的行为与构造函数的 explicit 相同。通常，这些推导规则也是模板。</p>
<p><code>explicit TemplateName(Parameters) -&gt; DeducedTemplate;</code></p>
<h4 id="1225-方法模板">12.2.5 方法模板 </h4>
<p>C++ 允许模板化类中的单个方法。这些方法称为<strong>方法模板</strong>（method template），它们可以在类模板中，也可以在非模板化的类中。在编写方法模板时，实际上是在为很多不同的类型编写很多不同版本的方法。在类模板中，方法模板对赋值运算符和复制构造函数非常有用。</p>
<blockquote>
<p><strong>警告：</strong></p>
<p><strong>不能用方法模板编写虚方法和析构函数。</strong></p>
</blockquote>
<p>考虑最早只有一个模板参数（即，元素类型）的 Grid 模板。可实例化很多不同类型的网格，例如 int 网格和 double 网格。</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>Grid<span class="token operator">&lt;</span><span class="token keyword keyword-int">int</span><span class="token operator">&gt;</span>    myIntGrid<span class="token punctuation">;</span>
Grid<span class="token operator">&lt;</span><span class="token keyword keyword-double">double</span><span class="token operator">&gt;</span> myDoubleGrid<span class="token punctuation">;</span>
</code></pre><p>然而，Grid&lt;int&gt; 和 Grid&lt;double&gt; 是两种不同的类型。如果编写的函数接收类型为 Grid&lt;double&gt; 的对象，就不能传入 Grid&lt;int&gt;。即使 int 网格中的元素可以复制到 double 网格中（因为 int 可以强制转换为 double），也不能将类型为 Grid&lt;int&gt; 的对象赋给类型为 Grid&lt;double&gt; 的对象，也不能从 Grid&lt;int&gt; 构造 Grid&lt;double&gt;。下面两行代码无法编译：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code>myDoubleGrid <span class="token operator">=</span> myIntGrid<span class="token punctuation">;</span>                <span class="token comment">// 无法编译</span>
Grid<span class="token operator">&lt;</span><span class="token keyword keyword-double">double</span><span class="token operator">&gt;</span> newDoubleGrid<span class="token punctuation">{</span>myIntGrid<span class="token punctuation">}</span><span class="token punctuation">;</span>   <span class="token comment">// 无法编译</span>
</code></pre><p>问题在于 Grid 模板的复制构造函数和赋值运算符如下所示：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token function">Grid</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Grid<span class="token operator">&amp;</span> src<span class="token punctuation">)</span><span class="token punctuation">;</span>
Grid<span class="token operator">&amp;</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Grid<span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>它们等同于：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token function">Grid</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Grid<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token operator">&amp;</span> src<span class="token punctuation">)</span><span class="token punctuation">;</span>
Grid<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token operator">&amp;</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Grid<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Grid 复制构造函数和 operator= 都会接收 const Grid&lt;T&gt; 的引用作为参数。当实例化 Grid&lt;double&gt;，并试图调用复制构造函数和 operator= 时，编译器通过这些原型生成方法：</p>
<pre data-role="codeBlock" data-info="c++" class="language-cpp c++"><code><span class="token function">Grid</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Grid<span class="token operator">&lt;</span><span class="token keyword keyword-double">double</span><span class="token operator">&gt;</span><span class="token operator">&amp;</span> src<span class="token punctuation">)</span><span class="token punctuation">;</span>
Grid<span class="token operator">&lt;</span><span class="token keyword keyword-double">double</span><span class="token operator">&gt;</span><span class="token operator">&amp;</span> <span class="token keyword keyword-operator">operator</span><span class="token operator">=</span><span class="token punctuation">(</span><span class="token keyword keyword-const">const</span> Grid<span class="token operator">&lt;</span><span class="token keyword keyword-double">double</span><span class="token operator">&gt;</span><span class="token operator">&amp;</span> rhs<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>注意，在生成的 Grid&lt;double&gt; 类中，构造函数或 operator= 都不会接收 Grid&lt;int&gt; 作为参数。</p>
<p>幸运的是，在 Grid 类中添加模板化方法的复制构造函数和赋值运算符，可生成一种将网格类型转换为另一种网格类型的方法，从而修复这个疏漏。下面是新的 Grid 类定义：</p>
<h2 id="16-c标准库概述">16 C++标准库概述 </h2>
<h3 id="162-c标准库概述">16.2 C++标准库概述 </h3>
<h4 id="1621-字符串">16.2.1 字符串 </h4>
<p>（参见第 2 章）</p>
<p>C++在 <em><strong>&lt;string&gt;</strong></em> 头文件提供内建的 string 类。</p>
<p>还提供在 <em><strong>&lt;string_view&gt;</strong></em> 中定****义的 string_view 类，这是各类字符串表示的只读视图，可用于简单替换 const string&amp;</p>
<h4 id="1622-正则表达式">16.2.2 正则表达式 </h4>
<p>（参见第 19 章）</p>
<p><em><strong>&lt;regex&gt;</strong></em> 头文件中提供了正则表达式</p>
<h4 id="1623-io流">16.2.3 I/O流 </h4>
<p>（参见第 13 章）</p>
<p>I/O功能在如下几个头文件中定义：</p>
<ul>
<li>&lt;fstream&gt;</li>
<li>&lt;iomanip&gt;</li>
<li>&lt;ios&gt;</li>
<li>&lt;iosfwd&gt;</li>
<li>&lt;iostream&gt; &lt;istream&gt; &lt;ostream&gt;</li>
<li>&lt;sstream&gt;</li>
<li>&lt;streambuf&gt;</li>
<li>&lt;strstream&gt;</li>
</ul>
<h4 id="1624-智能指针">16.2.4 智能指针 </h4>
<p>（参见第 7 章）</p>
<p>智能指针在 <em><strong>&lt;memory&gt;</strong></em> 头文件中定义</p>
<ul>
<li>unique_ptr</li>
<li>shared_ptr</li>
<li>week_ptr</li>
</ul>
<h4 id="1626-数学工具">16.2.6 数学工具 </h4>
<p>暂略</p>
<h4 id="16212-函数对象">16.2.12 函数对象 </h4>
<p>（参见第 18 章，18 章还详细论述标准库算法）</p>
<p>实现函数调用运算符的类称为函数对象</p>
<h4 id="16213-文件系统">16.2.13 文件系统 </h4>
<p>（参见第 20 章）</p>
<p>所有内容在 <em><strong>&lt;filesystem&gt;</strong></em> 头文件中定义，位于 std::filesytstem 名称空间。</p>
<h4 id="16216-标准整数类型">16.2.16 标准整数类型 </h4>
<p>（参见第30章）</p>
<p><em><strong>&lt;cstdint&gt;</strong></em> 头文件定义了大量标准整数类型，如 int8_t 和 int64_t 等</p>
<h4 id="16217-容器">16.2.17 容器 </h4>
<ol>
<li>vector<br>
非固定大小容器</li>
<li>list</li>
<li>forward_list</li>
<li>deque</li>
<li>array<br>
基本上是对C风格数组进行了简单包装，有固定大小</li>
<li>queue</li>
<li>priority_queue</li>
<li>stack</li>
<li>set 和 multiset</li>
<li>map 和 multimap</li>
<li>无序关联容器/哈希表
<ul>
<li>unordered_map</li>
<li>unordered_multimap</li>
<li>unordered_set</li>
<li>unordered_multiset</li>
</ul>
</li>
<li>bitset</li>
</ol>
<h4 id="16218-算法">16.2.18 算法 </h4>
<p>（参见第 17、18 章）</p>
<p>泛型算法，适用于大部分不同类型的容器</p>
<p>除非特别声明，否则这些算法都在 <strong>&lt;algorithm&gt;</strong> 头文件中定义</p>
<p>迭代器</p>
<table>
<thead>
<tr>
<th>函数名</th>
<th>概要</th>
</tr>
</thead>
<tbody>
<tr>
<td>begin()、end()</td>
<td>返回序列第一个和最后一个元素的非 const 迭代器</td>
</tr>
<tr>
<td>cbegin()、cend()</td>
<td>返回序列第一个和最后一个元素的 const 迭代器</td>
</tr>
<tr>
<td>rbegin()、rend()</td>
<td>返回序列第一个和最后一个元素的反向非 const 迭代器</td>
</tr>
<tr>
<td>crbegin()、cerend()</td>
<td>返回序列第一个和最后一个元素的反向 const 迭代器</td>
</tr>
</tbody>
</table>
<h5 id="非修改顺序算法">非修改顺序算法 </h5>
<p>查找元素的序列，返回一些有关元素的信息。因为是非修改类的算法，所以这些算法不会改变序列中元素的值或顺序</p>
<h6 id="搜索算法">搜索算法 </h6>
<table>
<thead>
<tr>
<th>算法名称</th>
<th>算法概要</th>
</tr>
</thead>
<tbody>
<tr>
<td>adjacent_find()</td>
<td>查找有两个连续元素或匹配谓词的第一个实例</td>
</tr>
<tr>
<td>find()、find_if()</td>
<td>查找第一个匹配值或使谓词返回 true 的元素</td>
</tr>
<tr>
<td>find_first_of()</td>
<td>与 find() 类似，只是同时搜索多个元素中的某个元素</td>
</tr>
<tr>
<td>find_if_not()</td>
<td>查找第一个使谓词返回 false 的元素</td>
</tr>
<tr>
<td>find_end()</td>
<td>在序列中查找最后一个匹配另一个序列的子序列，这个子序列的元素和谓词指定的一致</td>
</tr>
<tr>
<td>search()</td>
<td>功能同上，不过在序列中查找第一个匹配另一个序列的子序列</td>
</tr>
<tr>
<td>search_n()</td>
<td>查找前 n 个等于某个给定值或根据某个谓词和那个值相关的连续函数</td>
</tr>
</tbody>
</table>
<h6 id="比较算法">比较算法 </h6>
<table>
<thead>
<tr>
<th>算法名称</th>
<th>算法概要</th>
</tr>
</thead>
<tbody>
<tr>
<td>equal()</td>
<td>检查相应元素是否相等或匹配谓词，以判断两个序列是否相等</td>
</tr>
<tr>
<td>mismatch()</td>
<td>返回每个序列中第一个出现的和其他序列中同一位置元素不匹配的元素</td>
</tr>
<tr>
<td>lexicographical_compare()</td>
<td>略，去看书</td>
</tr>
</tbody>
</table>
<h6 id="计数算法">计数算法 </h6>
<table>
<thead>
<tr>
<th>算法名称</th>
<th>算法概要</th>
</tr>
</thead>
<tbody>
<tr>
<td>all_of()</td>
<td>如果序列为空，或谓词对序列所有元素都返回 true，则返回 true，否则返回 false</td>
</tr>
<tr>
<td>any_of()</td>
<td>如果谓词对序列中的至少一个元素返回 true，则返回 true，否则返回 false</td>
</tr>
<tr>
<td>none_of()</td>
<td>如果序列为空，或谓词对序列所有元素都返回 false，则返回 true，否则返回 false</td>
</tr>
<tr>
<td>count() count_if()</td>
<td>计算匹配某个值或使谓词返回 true的元素个数</td>
</tr>
</tbody>
</table>

      </div>
      <div class="md-sidebar-toc">
<div class="md-toc">
<details style="padding:0;;padding-left:0px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#c-高级编程学习笔记" class="md-toc-link"><p>C++ 高级编程学习笔记</p>
</a>
          </summary>
        <div>
          <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1-c和标准库速成" class="md-toc-link"><p>1 C++和标准库速成</p>
</a>
          </summary>
        <div>
          <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#11-c基础知识" class="md-toc-link"><p>1.1 c++基础知识</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1111-三向比较运算符" class="md-toc-link">
            <p>1.1.11 三向比较运算符</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1113--属性" class="md-toc-link"><p>1.1.13  属性</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#nodiscard" class="md-toc-link">
            <p>nodiscard</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#maybe_unused" class="md-toc-link">
            <p>maybe_unused</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#noreturn" class="md-toc-link">
            <p>noreturn</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#deprecated" class="md-toc-link">
            <p>deprecated</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#likely-和-unlikely" class="md-toc-link">
            <p>likely 和 unlikely</p>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1115-stdarray" class="md-toc-link">
            <p>1.1.15 std::array</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1116-stdvector" class="md-toc-link">
            <p>1.1.16 std::vector</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1117-stdpair" class="md-toc-link">
            <p>1.1.17 std::pair</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1118-stdoptional" class="md-toc-link">
            <p>1.1.18 std::optional</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1119-结构化绑定" class="md-toc-link">
            <p>1.1.19 结构化绑定</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1120-循环" class="md-toc-link">
            <p>1.1.20 循环</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1121-初始化列表" class="md-toc-link">
            <p>1.1.21 初始化列表</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1125-统一初始化" class="md-toc-link"><p>1.1.25 统一初始化</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#指派初始化器" class="md-toc-link">
            <p>指派初始化器</p>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1126-指针和动态内存" class="md-toc-link"><p>1.1.26 指针和动态内存</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#堆栈和堆" class="md-toc-link">
            <p>堆栈和堆</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#使用指针" class="md-toc-link">
            <p>使用指针</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#动态分配和数组" class="md-toc-link">
            <p>动态分配和数组</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#空指针常量" class="md-toc-link">
            <p>空指针常量</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#智能指针" class="md-toc-link">
            <p>智能指针</p>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1127-const-的用法" class="md-toc-link"><p>1.1.27 const 的用法</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#const-修饰类型" class="md-toc-link">
            <p>const 修饰类型</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#const-方法" class="md-toc-link">
            <p>const 方法</p>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1128-constexpr-关键字" class="md-toc-link">
            <p>1.1.28 constexpr 关键字</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1129-consteval-关键字" class="md-toc-link">
            <p>1.1.29 consteval 关键字</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1130-引用" class="md-toc-link"><p>1.1.30 引用</p>
</a>
          </summary>
        <div>
          <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1-引用变量" class="md-toc-link"><ol>
<li>引用变量</li>
</ol>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#修改引用" class="md-toc-link">
            <p>修改引用</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#const-引用" class="md-toc-link">
            <p>const 引用</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#指针的引用和引用的指针" class="md-toc-link">
            <p>指针的引用和引用的指针</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#结构化绑定和引用" class="md-toc-link">
            <p>结构化绑定和引用</p>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#2-引用数据成员" class="md-toc-link">
            <ol start="2">
<li>引用数据成员</li>
</ol>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#3-引用作为函数参数" class="md-toc-link"><ol start="3">
<li>引用作为函数参数</li>
</ol>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#const-引用传递" class="md-toc-link">
            <p>const 引用传递</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#值传递和引用传递" class="md-toc-link">
            <p>值传递和引用传递</p>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#4-引用作为返回值" class="md-toc-link">
            <ol start="4">
<li>引用作为返回值</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#5-在引用与指针之间抉择" class="md-toc-link">
            <ol start="5">
<li>在引用与指针之间抉择</li>
</ol>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1131-const_cast" class="md-toc-link">
            <p>1.1.31 const_cast()</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1132-异常" class="md-toc-link">
            <p>1.1.32 异常</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1135-类型推断" class="md-toc-link"><p>1.1.35 类型推断</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1-关键字-auto" class="md-toc-link">
            <ol>
<li>关键字 auto</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#2-关键字-decltype" class="md-toc-link">
            <ol start="2">
<li>关键字 decltype</li>
</ol>

          </a></div>
        </div>
      </details>
    
        </div>
      </details>
    
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#2-使用-string-和-string_view" class="md-toc-link"><p>2 使用 string 和 string_view</p>
</a>
          </summary>
        <div>
          <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#21-动态字符串" class="md-toc-link"><p>2.1 动态字符串</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#212-字符串字面量" class="md-toc-link">
            <p>2.1.2 字符串字面量</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#213-c-stdstring-类" class="md-toc-link">
            <p>2.1.3 C++ std::string 类</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#214-stdstring_view-类" class="md-toc-link">
            <p>2.1.4 std::string_view 类</p>

          </a></div>
        </div>
      </details>
    
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#3-编码风格" class="md-toc-link"><p>3 编码风格</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#32-为代码编写文档" class="md-toc-link">
            <p>3.2 为代码编写文档</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#33-分解" class="md-toc-link"><p>3.3 分解</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#331-通过重构分解" class="md-toc-link">
            <p>3.3.1 通过重构分解</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#332-通过设计来分解" class="md-toc-link">
            <p>3.3.2 通过设计来分解</p>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#34-命名" class="md-toc-link"><p>3.4 命名</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#341-选择恰当的名称" class="md-toc-link">
            <p>3.4.1 选择恰当的名称</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#342-命名约定" class="md-toc-link">
            <p>3.4.2 命名约定</p>

          </a></div>
        </div>
      </details>
    
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#6-设计可重用代码" class="md-toc-link"><p>6 设计可重用代码</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#61-重用哲学" class="md-toc-link">
            <p>6.1 重用哲学</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#62-如何设计可重用代码" class="md-toc-link"><p>6.2 如何设计可重用代码</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#621-使用抽象" class="md-toc-link">
            <p>6.2.1 使用抽象</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#622-构建理想的重用代码" class="md-toc-link"><p>6.2.2 构建理想的重用代码</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1-避免组合不相干的概念或者逻辑上独立的概念" class="md-toc-link">
            <ol>
<li>避免组合不相干的概念或者逻辑上独立的概念</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#2-对泛型数据结构和算法使用模板" class="md-toc-link">
            <ol start="2">
<li>对泛型数据结构和算法使用模板</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#3-提供恰当的检测和安全措施" class="md-toc-link">
            <ol start="3">
<li>提供恰当的检测和安全措施</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#4-扩展性" class="md-toc-link">
            <ol start="4">
<li>扩展性</li>
</ol>

          </a></div>
        </div>
      </details>
    
        </div>
      </details>
    
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#7-内存管理" class="md-toc-link"><p>7 内存管理</p>
</a>
          </summary>
        <div>
          <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#71-使用动态内存" class="md-toc-link"><p>7.1 使用动态内存</p>
</a>
          </summary>
        <div>
          <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#712-分配与释放" class="md-toc-link"><p>7.1.2 分配与释放</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1-使用-new-和-delete" class="md-toc-link">
            <ol>
<li>使用 new 和 delete</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#2-关于-malloc-函数" class="md-toc-link">
            <ol start="2">
<li>关于 malloc() 函数</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#3-当内存分配失败时" class="md-toc-link">
            <ol start="3">
<li>当内存分配失败时</li>
</ol>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#713-数组" class="md-toc-link"><p>7.1.3 数组</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1-基本类型的数组" class="md-toc-link">
            <ol>
<li>基本类型的数组</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#2-对象的数组" class="md-toc-link">
            <ol start="2">
<li>对象的数组</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#3-删除数组" class="md-toc-link">
            <ol start="3">
<li>删除数组</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#4-多维数组" class="md-toc-link">
            <ol start="4">
<li>多维数组</li>
</ol>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#731-指针运算" class="md-toc-link">
            <p>7.3.1 指针运算</p>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#74-智能指针" class="md-toc-link"><p>7.4 智能指针</p>
</a>
          </summary>
        <div>
          <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#741-unique_ptr" class="md-toc-link"><p>7.4.1 unique_ptr</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1-创建-unique_ptrs" class="md-toc-link">
            <ol>
<li>创建 unique_ptrs</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#2-使用-unique_ptrs" class="md-toc-link">
            <ol start="2">
<li>使用 unique_ptrs</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#3-unique_ptr-和-c-风格数组" class="md-toc-link">
            <ol start="3">
<li>unique_ptr 和 C 风格数组</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#4-自定义-deleter" class="md-toc-link">
            <ol start="4">
<li>自定义 deleter</li>
</ol>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#742-shared_ptr" class="md-toc-link"><p>7.4.2 shared_ptr</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1-强制转换-shared_ptr" class="md-toc-link">
            <ol>
<li>强制转换 shared_ptr</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#2-引用计数的必要性" class="md-toc-link">
            <ol start="2">
<li>引用计数的必要性</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#3-别名" class="md-toc-link">
            <ol start="3">
<li>别名</li>
</ol>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#743-weak_ptr" class="md-toc-link">
            <p>7.4.3 weak_ptr</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#744-移动语义" class="md-toc-link">
            <p>7.4.4 移动语义</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#745-enable_shared_from_this" class="md-toc-link">
            <p>7.4.5 enable_shared_from_this</p>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#75-常见的内存陷阱" class="md-toc-link"><p>7.5 常见的内存陷阱</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#751-分配不足的字符串" class="md-toc-link">
            <p>7.5.1 分配不足的字符串</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#752-访问内存越界" class="md-toc-link">
            <p>7.5.2 访问内存越界</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#753-内存泄漏" class="md-toc-link">
            <p>7.5.3 内存泄漏</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#754-双重删除和无效指针" class="md-toc-link">
            <p>7.5.4 双重删除和无效指针</p>

          </a></div>
        </div>
      </details>
    
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#8-熟悉类和对象" class="md-toc-link"><p>8 熟悉类和对象</p>
</a>
          </summary>
        <div>
          <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#82-编写类" class="md-toc-link"><p>8.2 编写类</p>
</a>
          </summary>
        <div>
          <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#821-类定义" class="md-toc-link"><p>8.2.1 类定义</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1-类的成员" class="md-toc-link">
            <ol>
<li>类的成员</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#2-访问控制" class="md-toc-link">
            <ol start="2">
<li>访问控制</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#3-声明顺序" class="md-toc-link">
            <ol start="3">
<li>声明顺序</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#4-类内成员初始化器" class="md-toc-link">
            <ol start="4">
<li>类内成员初始化器</li>
</ol>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#822-定义方法" class="md-toc-link">
            <p>8.2.2 定义方法</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#823-使用对象" class="md-toc-link">
            <p>8.2.3 使用对象</p>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#83-对象的生命周期" class="md-toc-link"><p>8.3 对象的生命周期</p>
</a>
          </summary>
        <div>
          <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#831-创建对象" class="md-toc-link"><p>8.3.1 创建对象</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1-编写构造函数" class="md-toc-link">
            <ol>
<li>编写构造函数</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#2-使用构造函数" class="md-toc-link">
            <ol start="2">
<li>使用构造函数</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#3-提供多个构造函数" class="md-toc-link">
            <ol start="3">
<li>提供多个构造函数</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#4-默认构造函数" class="md-toc-link">
            <ol start="4">
<li>默认构造函数</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#5-构造函数初始化器" class="md-toc-link">
            <ol start="5">
<li>构造函数初始化器</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#6-复制构造函数" class="md-toc-link">
            <ol start="6">
<li>复制构造函数</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#7-初始化列表构造函数" class="md-toc-link">
            <ol start="7">
<li>初始化列表构造函数</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#8-委托构造函数" class="md-toc-link">
            <ol start="8">
<li>委托构造函数</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#9-总结编译器生成的构造函数" class="md-toc-link">
            <ol start="9">
<li>总结编译器生成的构造函数</li>
</ol>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#832-销毁对象" class="md-toc-link">
            <p>8.3.2 销毁对象</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#833-对象赋值" class="md-toc-link"><p>8.3.3 对象赋值</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1-声明赋值运算符" class="md-toc-link">
            <ol>
<li>声明赋值运算符</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#2-定义赋值运算符" class="md-toc-link">
            <ol start="2">
<li>定义赋值运算符</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#3-显式地默认或删除赋值运算符" class="md-toc-link">
            <ol start="3">
<li>显式地默认或删除赋值运算符</li>
</ol>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#835-复制和赋值的区别" class="md-toc-link">
            <p>8.3.5 复制和赋值的区别</p>

          </a></div>
        </div>
      </details>
    
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#9-精通类与对象" class="md-toc-link"><p>9 精通类与对象</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#91-友元" class="md-toc-link">
            <p>9.1 友元</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#92-对象中的动态内存分配" class="md-toc-link"><p>9.2 对象中的动态内存分配</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#922-使用析构函数释放内存" class="md-toc-link">
            <p>9.2.2 使用析构函数释放内存</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#923-处理复制和赋值" class="md-toc-link">
            <p>9.2.3 处理复制和赋值</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#924-使用移动语义处理移动" class="md-toc-link"><p>9.2.4 使用移动语义处理移动</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1-右值引用" class="md-toc-link">
            <ol>
<li>右值引用</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#2-实现移动语义" class="md-toc-link">
            <ol start="2">
<li>实现移动语义</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#3-测试移动操作" class="md-toc-link">
            <ol start="3">
<li>测试移动操作</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#4-使用移动语义实现交换函数" class="md-toc-link">
            <ol start="4">
<li>使用移动语义实现交换函数</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#5-在返回语句中使用-stdmove" class="md-toc-link">
            <ol start="5">
<li>在返回语句中使用 std::move()</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#6-向函数传递参数的最佳方法" class="md-toc-link">
            <ol start="6">
<li>向函数传递参数的最佳方法</li>
</ol>

          </a></div>
        </div>
      </details>
    
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#93-与方法有关的更多内容" class="md-toc-link"><p>9.3 与方法有关的更多内容</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#931-static-方法" class="md-toc-link">
            <p>9.3.1 static 方法</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#932-const方法" class="md-toc-link"><p>9.3.2 const方法</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#mutable-数据成员" class="md-toc-link">
            <p>mutable 数据成员</p>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#933-方法重载" class="md-toc-link"><p>9.3.3 方法重载</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1-基于-const-的重载" class="md-toc-link">
            <ol>
<li>基于 const 的重载</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#2-显式删除重载" class="md-toc-link">
            <ol start="2">
<li>显式删除重载</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#3-引用限定方法" class="md-toc-link">
            <ol start="3">
<li>引用限定方法</li>
</ol>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#934-内联方法" class="md-toc-link">
            <p>9.3.4 内联方法</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#935-默认参数" class="md-toc-link">
            <p>9.3.5 默认参数</p>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#94-不同的数据成员类型" class="md-toc-link"><p>9.4 不同的数据成员类型</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#941-静态数据成员" class="md-toc-link">
            <p>9.4.1 静态数据成员</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#942-const-static-数据成员" class="md-toc-link">
            <p>9.4.2 const static 数据成员</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#943-引用数据成员" class="md-toc-link">
            <p>9.4.3 引用数据成员</p>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#95-嵌套类" class="md-toc-link">
            <p>9.5 嵌套类</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#96-类内的枚举类型" class="md-toc-link">
            <p>9.6 类内的枚举类型</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#97-运算符重载" class="md-toc-link"><p>9.7 运算符重载</p>
</a>
          </summary>
        <div>
          <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#971-示例为-spreadsheetcell-实现加法" class="md-toc-link"><p>9.7.1 示例：为 SpreadsheetCell 实现加法</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1-首次尝试add-加法" class="md-toc-link">
            <ol>
<li>首次尝试：add 加法</li>
</ol>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#2-第二次尝试将-operator-作为方法重载" class="md-toc-link"><ol start="2">
<li>第二次尝试：将 operator+ 作为方法重载</li>
</ol>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#隐式转换" class="md-toc-link">
            <p>隐式转换</p>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#3-第三次尝试全局-operator" class="md-toc-link">
            <ol start="3">
<li>第三次尝试：全局 operator+</li>
</ol>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#972-重载算术运算符" class="md-toc-link"><p>9.7.2 重载算术运算符</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#重载简写算术运算符" class="md-toc-link">
            <p>重载简写算术运算符</p>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#973-重载比较运算符" class="md-toc-link">
            <p>9.7.3 重载比较运算符</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#974-创建具有运算符重载的类型" class="md-toc-link">
            <p>9.7.4 创建具有运算符重载的类型</p>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#98-创建稳定的接口" class="md-toc-link"><p>9.8 创建稳定的接口</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#使用接口类和实现类" class="md-toc-link">
            <p>使用接口类和实现类</p>

          </a></div>
        </div>
      </details>
    
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#10-揭秘继承技术" class="md-toc-link"><p>10 揭秘继承技术</p>
</a>
          </summary>
        <div>
          <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#101-使用继承构建类" class="md-toc-link"><p>10.1 使用继承构建类</p>
</a>
          </summary>
        <div>
          <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1012-重写方法" class="md-toc-link"><p>10.1.2 重写方法</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#关键字-virtual" class="md-toc-link">
            <p>关键字 virtual</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#重写方法的语法" class="md-toc-link">
            <p>重写方法的语法</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#客户对重写方法的看法" class="md-toc-link">
            <p>客户对重写方法的看法</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#override-关键字" class="md-toc-link">
            <p>override 关键字</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#virtual-的真相" class="md-toc-link"><p>virtual 的真相</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#隐藏而不是重写" class="md-toc-link">
            <p>隐藏而不是重写</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#如何实现-virtual" class="md-toc-link">
            <p>如何实现 virtual</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#使用-virtual-的理由" class="md-toc-link">
            <p>使用 virtual 的理由</p>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#禁用重写" class="md-toc-link">
            <p>禁用重写</p>

          </a></div>
        </div>
      </details>
    
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#103-利用父类" class="md-toc-link"><p>10.3 利用父类</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1031-父类构造函数" class="md-toc-link">
            <p>10.3.1 父类构造函数</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1032-父类的析构函数" class="md-toc-link">
            <p>10.3.2 父类的析构函数</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1033-使用父类方法" class="md-toc-link">
            <p>10.3.3 使用父类方法</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1034-向上转型和向下转型" class="md-toc-link">
            <p>10.3.4 向上转型和向下转型</p>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#104-继承与多态性" class="md-toc-link"><p>10.4 继承与多态性</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1042-设计多态性的电子表格单元格" class="md-toc-link">
            <p>10.4.2 设计多态性的电子表格单元格</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1043-spreadsheetcell-基类" class="md-toc-link"><p>10.4.3 SpreadsheetCell 基类</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#初次尝试" class="md-toc-link">
            <p>初次尝试</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#纯虚方法和抽象基类" class="md-toc-link">
            <p>纯虚方法和抽象基类</p>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1044-独立的派生类" class="md-toc-link"><p>10.4.4 独立的派生类</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#stringspreadsheetcell-类的定义" class="md-toc-link">
            <p>StringSpreadsheetCell 类的定义</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#stringspreadsheetcell-的实现" class="md-toc-link">
            <p>StringSpreadsheetCell 的实现</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#doublespreadsheetcell-类的定义和实现" class="md-toc-link">
            <p>DoubleSpreadsheetCell 类的定义和实现</p>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1045-利用多态性" class="md-toc-link">
            <p>10.4.5 利用多态性</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1045-考虑将来" class="md-toc-link">
            <p>10.4.5 考虑将来</p>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#105-多重继承" class="md-toc-link"><p>10.5 多重继承</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1051-从多个类继承" class="md-toc-link">
            <p>10.5.1 从多个类继承</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1052-名称冲突和歧义基类" class="md-toc-link"><p>10.5.2 名称冲突和歧义基类</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#名称歧义" class="md-toc-link">
            <p>名称歧义</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#歧义基类" class="md-toc-link">
            <p>歧义基类</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#多重继承的用途" class="md-toc-link">
            <p>多重继承的用途</p>

          </a></div>
        </div>
      </details>
    
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#106-有趣而晦涩的继承问题" class="md-toc-link"><p>10.6 有趣而晦涩的继承问题</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1061-修改重写方法的返回类型" class="md-toc-link">
            <p>10.6.1 修改重写方法的返回类型</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1062-派生类中添加虚基类方法的重载" class="md-toc-link">
            <p>10.6.2 派生类中添加虚基类方法的重载</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1063-继承的构造函数" class="md-toc-link"><p>10.6.3 继承的构造函数</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#隐藏继承的构造函数" class="md-toc-link">
            <p>隐藏继承的构造函数</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#继承的构造函数和多重继承" class="md-toc-link">
            <p>继承的构造函数和多重继承</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#数据成员的初始化" class="md-toc-link">
            <p>数据成员的初始化</p>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1064-重写时的特殊情况" class="md-toc-link"><p>10.6.4 重写时的特殊情况</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#静态基类方法" class="md-toc-link">
            <p>静态基类方法</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#重载基类方法" class="md-toc-link">
            <p>重载基类方法</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#基类的-private-方法" class="md-toc-link">
            <p>基类的 private 方法</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#基类方法具有默认参数" class="md-toc-link">
            <p>基类方法具有默认参数</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#基类方法具有不同的访问级别" class="md-toc-link">
            <p>基类方法具有不同的访问级别</p>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1065-派生类中的复制构造函数和赋值运算符" class="md-toc-link">
            <p>10.6.5 派生类中的复制构造函数和赋值运算符</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1066-运行期类型工具" class="md-toc-link">
            <p>10.6.6 运行期类型工具</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1067-非-public-继承" class="md-toc-link">
            <p>10.6.7 非 public 继承</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1068-虚基类" class="md-toc-link">
            <p>10.6.8 虚基类</p>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#107-类型转换" class="md-toc-link"><p>10.7 类型转换</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1071-static_cast" class="md-toc-link">
            <p>10.7.1 static_cast()</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1072-reinterpret_cast" class="md-toc-link">
            <p>10.7.2 reinterpret_cast()</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1073-stdbit_cast" class="md-toc-link">
            <p>10.7.3 std::bit_cast()</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1074-dynamic_cast" class="md-toc-link">
            <p>10.7.4 dynamic_cast()</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1075-类型转换小结" class="md-toc-link">
            <p>10.7.5 类型转换小结</p>

          </a></div>
        </div>
      </details>
    
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#11-零碎的工作" class="md-toc-link"><p>11 零碎的工作</p>
</a>
          </summary>
        <div>
          <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#111-模块" class="md-toc-link"><p>11.1 模块</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1111-模块接口文件" class="md-toc-link">
            <p>11.1.1 模块接口文件</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1112-模块实现文件" class="md-toc-link">
            <p>11.1.2 模块实现文件</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1113-从实现中分离接口" class="md-toc-link">
            <p>11.1.3 从实现中分离接口</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#可见性和可访问性" class="md-toc-link">
            <p>可见性和可访问性</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1115-子模块" class="md-toc-link">
            <p>11.1.5 子模块</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1116-模块划分" class="md-toc-link"><p>11.1.6 模块划分</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#实现分区" class="md-toc-link">
            <p>实现分区</p>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1117-头文件单元" class="md-toc-link">
            <p>11.1.7 头文件单元</p>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#112-头文件" class="md-toc-link"><p>11.2 头文件</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1121-重复定义" class="md-toc-link">
            <p>11.21 重复定义</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1122-循环依赖" class="md-toc-link">
            <p>11.2.2 循环依赖</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1123-查询头文件是否存在" class="md-toc-link">
            <p>11.2.3 查询头文件是否存在</p>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#113-核心语言特性的特性测试宏" class="md-toc-link">
            <p>11.3 核心语言特性的特性测试宏</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#114-static-关键字" class="md-toc-link"><p>11.4 static 关键字</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1141-静态数据成员和方法" class="md-toc-link">
            <p>11.4.1 静态数据成员和方法</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1142-静态链接" class="md-toc-link">
            <p>11.4.2 静态链接</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1143-函数中的静态变量" class="md-toc-link">
            <p>11.4.3 函数中的静态变量</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1144-非局部变量的初始化顺序" class="md-toc-link">
            <p>11.4.4 非局部变量的初始化顺序</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1145-非局部变量的销毁顺序" class="md-toc-link">
            <p>11.4.5 非局部变量的销毁顺序</p>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#115-c-的实用工具" class="md-toc-link"><p>11.5 C 的实用工具</p>
</a>
          </summary>
        <div>
          <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1151-边长参数列表" class="md-toc-link"><p>11.5.1 边长参数列表</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#访问参数" class="md-toc-link">
            <p>访问参数</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#为什么不应该使用-c-风格的边长参数列表" class="md-toc-link">
            <p>为什么不应该使用 C 风格的边长参数列表</p>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1152-预处理器宏" class="md-toc-link">
            <p>11.5.2 预处理器宏</p>

          </a></div>
        </div>
      </details>
    
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#12-利用模板编写泛型代码" class="md-toc-link"><p>12 利用模板编写泛型代码</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#121-模板概述" class="md-toc-link">
            <p>12.1 模板概述</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#122-类模板" class="md-toc-link"><p>12.2 类模板</p>
</a>
          </summary>
        <div>
          <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1221-编写类模板" class="md-toc-link"><p>12.2.1 编写类模板</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#编写不使用模板的代码" class="md-toc-link">
            <p>编写不使用模板的代码</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#模板-grid-类" class="md-toc-link"><p>模板 Grid 类</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#grid-类定义" class="md-toc-link">
            <p>Grid 类定义</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#grid-类的方法定义" class="md-toc-link">
            <p>Grid 类的方法定义</p>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#使用-grid-模板" class="md-toc-link">
            <p>使用 Grid 模板</p>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1222-编译器处理模板的原理" class="md-toc-link"><p>12.2.2 编译器处理模板的原理</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1-选择性实例化" class="md-toc-link">
            <ol>
<li>选择性实例化</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#2-模板对类型的要求" class="md-toc-link">
            <ol start="2">
<li>模板对类型的要求</li>
</ol>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1223-将模板代码分布到多个文件中" class="md-toc-link"><p>12.2.3 将模板代码分布到多个文件中</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1-将方法的定义与模板的定义放在相同文件中" class="md-toc-link">
            <ol>
<li>将方法的定义与模板的定义放在相同文件中</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#2-将方法的定义放在单独的文件中" class="md-toc-link">
            <ol start="2">
<li>将方法的定义放在单独的文件中</li>
</ol>

          </a></div>
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#1224-模板参数" class="md-toc-link"><p>12.2.4 模板参数</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1-非类型的模板参数" class="md-toc-link">
            <ol>
<li>非类型的模板参数</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#2-类型参数的默认值" class="md-toc-link">
            <ol start="2">
<li>类型参数的默认值</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#3-类模板的实参推导" class="md-toc-link">
            <ol start="3">
<li>类模板的实参推导</li>
</ol>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#用户定义的推导规则" class="md-toc-link">
            <p>用户定义的推导规则</p>

          </a></div>
        </div>
      </details>
    <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1225-方法模板" class="md-toc-link">
            <p>12.2.5 方法模板</p>

          </a></div>
        </div>
      </details>
    
        </div>
      </details>
    <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#16-c标准库概述" class="md-toc-link"><p>16 C++标准库概述</p>
</a>
          </summary>
        <div>
          <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#162-c标准库概述" class="md-toc-link"><p>16.2 C++标准库概述</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1621-字符串" class="md-toc-link">
            <p>16.2.1 字符串</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1622-正则表达式" class="md-toc-link">
            <p>16.2.2 正则表达式</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1623-io流" class="md-toc-link">
            <p>16.2.3 I/O流</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1624-智能指针" class="md-toc-link">
            <p>16.2.4 智能指针</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#1626-数学工具" class="md-toc-link">
            <p>16.2.6 数学工具</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#16212-函数对象" class="md-toc-link">
            <p>16.2.12 函数对象</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#16213-文件系统" class="md-toc-link">
            <p>16.2.13 文件系统</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#16216-标准整数类型" class="md-toc-link">
            <p>16.2.16 标准整数类型</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#16217-容器" class="md-toc-link">
            <p>16.2.17 容器</p>

          </a></div><details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#16218-算法" class="md-toc-link"><p>16.2.18 算法</p>
</a>
          </summary>
        <div>
          <details style="padding:0;;padding-left:24px;" open="">
        <summary class="md-toc-link-wrapper">
          <a href="#非修改顺序算法" class="md-toc-link"><p>非修改顺序算法</p>
</a>
          </summary>
        <div>
          <div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#搜索算法" class="md-toc-link">
            <p>搜索算法</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#比较算法" class="md-toc-link">
            <p>比较算法</p>

          </a></div><div class="md-toc-link-wrapper" style="padding:0;;display:list-item;list-style:square;margin-left:42px">
          <a href="#计数算法" class="md-toc-link">
            <p>计数算法</p>

          </a></div>
        </div>
      </details>
    
        </div>
      </details>
    
        </div>
      </details>
    
        </div>
      </details>
    
        </div>
      </details>
    
</div>
</div>
      <a id="sidebar-toc-btn">≡</a>
    
    
    <script type="module">
// TODO: If ZenUML gets integrated into mermaid in the future,
//      we can remove the following lines.


var MERMAID_CONFIG = ({"startOnLoad":false});
if (typeof MERMAID_CONFIG !== 'undefined') {
  MERMAID_CONFIG.startOnLoad = false
  MERMAID_CONFIG.cloneCssStyles = false
  MERMAID_CONFIG.theme = "default"
}

mermaid.initialize(MERMAID_CONFIG || {})
if (typeof(window['Reveal']) !== 'undefined') {
  function mermaidRevealHelper(event) {
    var currentSlide = event.currentSlide
    var diagrams = currentSlide.querySelectorAll('.mermaid')
    for (var i = 0; i < diagrams.length; i++) {
      var diagram = diagrams[i]
      if (!diagram.hasAttribute('data-processed')) {
        mermaid.init(null, diagram, ()=> {
          Reveal.slide(event.indexh, event.indexv)
        })
      }
    }
  }
  Reveal.addEventListener('slidetransitionend', mermaidRevealHelper)
  Reveal.addEventListener('ready', mermaidRevealHelper)
  await mermaid.run({
    nodes: document.querySelectorAll('.mermaid')
  })
} else {
  await mermaid.run({
    nodes: document.querySelectorAll('.mermaid')
  })
}
</script>
    
    
    
<script>

var sidebarTOCBtn = document.getElementById('sidebar-toc-btn')
sidebarTOCBtn.addEventListener('click', function(event) {
  event.stopPropagation()
  if (document.body.hasAttribute('html-show-sidebar-toc')) {
    document.body.removeAttribute('html-show-sidebar-toc')
  } else {
    document.body.setAttribute('html-show-sidebar-toc', true)
  }
})
</script>
      
  
    </body></html>